How can I share snapshots between Jest tests?
Using TDD I'd like to write some new tests that create data in slightly different ways, and verify that that test data gets sanitized down to the same data as a previous test.
So after writing Test 1 and generating a snapshot, Test 2/3/4 should generate the same snapshot as Test 1.
How can I make that happen? Jest appears to prepend the test name to custom snapshot names so I can't use .match(test1name)
.
(Using all-new identical snapshots for each test bloats the snapshots file and seems far from ideal.)
See also questions close to this topic
-
Testing vue 3 async setup
I can't quite figure out how to test my async setup vue 3 component.
<script> import { someRepository } from "@/repositories" import { onMounted, toRefs } from 'vue' export default { name: 'HelloWorld', props: { msg: String }, async setup(props) { const { msg } = toRefs(props) console.log(await someRepository.get()) return { msg } } } </script>
Wrapping this component with a
Suspense
component works as expected. However when running tests:import { someRepository } from "@/repositories" import { mount } from '@vue/test-utils' import HelloWorld from './HelloWorld.vue' jest.mock("@/repositories", () => { return { someRepository: { get: jest.fn() } } }) describe('HelloWorld.vue', () => { beforeEach(() => { someRepository.get.mockImplementation(() => Promise.resolve("asdasd")) }); it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = mount(HelloWorld, { props: { msg } }) expect(wrapper.text()).toMatch(msg) }) }
This will throw an error
TypeError: Cannot read property 'addEventListener' of null
on theshallowMount
ormount
. I've also tried making them async but that also doesn't work throwing the same error.The only solution is using
onMounted
hook. But that's not how Suspense is supposed to be used. Any ideas? Or is this simply not yet supported? -
How to use jest to mock a function just for some specified arguments, but uses its original logic for any other arguments?
Say I have a function:
function complexCompute(num: number): string { switch(num) { case 1: return '...something...'; case 2: return '...something...'; case 3: return '...something...'; // more cases }
It is used many times in the code I want to test, but I want to mock it like this:
- when I pass the argument
num
is1
,complexCompute
returns my mocked stringmy-mock-string1
- when I pass any other argument, it uses its original logic
I can't find a way to do it, since if I mocked the module:
jest.mock('./complexCompute')
The mocked
complexCompute
doesn't have original logic. I have to define the mock value for argument1
, and also rewrite the whole logic for other arguments.Is there any way to do it?
- when I pass the argument
-
Jest ReferenceError: You are trying to `import` a file after the Jest environment has been torn down
I am trying to test my APIs using jest.
I am having this error show after each time I run the tests
PASS test/auth.test.js √ Create a new user (237 ms) √ SignIn with user (133 ms) √ Delete The user (144 ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 2.173 s, estimated 8 s Ran all test suites. ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
my test file
const auth = require("../routes/auth"); const fetch = require('node-fetch'); fetch.Promise = require("bluebird"); (async () => { beforeAll(done =>{ jest.useFakeTimers(); done(); }) afterAll(done => { done(); }) jest.setTimeout(10000) test("Create a new user", async () => { const status = await fetch(URL + "/api/auth/signUp", { method:"POST", headers: { 'Content-Type': 'application/json;charset=utf-8' }, body:JSON.stringify(SignUpReq) }) .then(res => res.status) await expect(status).toBe(201); }) test("SignIn with user", async () => { let status = await fetch(URL + "/api/auth/signin", { method:"POST", headers: { 'Content-Type': 'application/json;charset=utf-8' }, body:JSON.stringify(deleteUserReq) }) .then(res => res.status) await expect(status).toBe(200); }) test("Delete The user", async () => { const status = await fetch(URL + "/api/auth/deleteUser", { method:"DELETE", headers: { 'Content-Type': 'application/json;charset=utf-8', 'Authorization': 'Bearer <token>' }, body:JSON.stringify(deleteUserReq) }) .then(res => res.status) await expect(status).toBe(200); }) })()
The APIs test the actual database, not a mock DB
I tried
mongoose.connection.close()
jest.useFakeTimers();
changing jest timeout
adding done before and after all
using await before each expect
still, the error shows up
my env:
"node": "V12.19.0" "bcrypt": "^5.0.0", "bluebird": "^3.7.2", "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "md5": "^2.3.0", "mongoose": "^5.11.8", "node-fetch": "^2.6.1", "jest": "^26.6.3"
my DB is hosted on mongo atlas
How can I remove this error message?
-
a PHPpunit test fail randomly
I have a Quiz class. This class load 10 questions from a database depending on the level and the type of the quiz Object: level 0 load the ten first, level 1 load the next ten and so on.
So in my test i create in a test database 30 questions. Then i create quiz object with different level and i check that the first question in the quiz steps array match what i expect.
This test "quiz_contain_steps_depending_on_type_and_level()" failed randomly at least once every 5 launches.
This is the QuizTest class
<?php namespace App\Tests\Quiz; use App\Quiz\Question; use App\Quiz\Quiz; use App\Quiz\QuizQuestionRepositoryManager; use App\Quiz\QuizStep; use App\Quiz\QuizType; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectRepository; use Faker\Factory; use Faker\Generator; use ReflectionException; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Config\Definition\Exception\Exception; class QuizTest extends KernelTestCase { use QuestionLoremTrait; use PrivatePropertyValueTestTrait; private Generator $faker; private ?EntityManagerInterface $em; private ObjectRepository $questionRepo; private QuizQuestionRepositoryManager $quizQuestionManager; protected function setUp(): void { $kernel = self::bootKernel(); $this->faker = Factory::create(); $this->em = $kernel->getContainer()->get('doctrine')->getManager(); $this->em->getConnection()->beginTransaction(); $this->questionRepo = $kernel->getContainer()->get('doctrine')->getRepository(Question::class); $this->quizQuestionManager = new QuizQuestionRepositoryManager($this->questionRepo); } protected function tearDown(): void { parent::tearDown(); $this->em->getConnection()->rollBack(); $this->em->close(); $this->em = null; } /** * @test * @dataProvider provideQuizDataAndFirstQuestionExpectedIndex * @param array $quizData * @param int $firstQuestionExpectedIndex * @throws ReflectionException * @throws \Exception */ public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex) { //We have questions in db $questions = []; for ($q = 1; $q <= 30; $q++) { $question = $this->persistLoremQuestion($this->faker, $this->em); $questions[] = $question; } $this->em->flush(); //When we create Quiz instance $quiz $quiz = new Quiz($this->quizQuestionManager,quizData: $quizData); //When we look at this $quiz steps property $quizSteps = $quiz->getSteps(); /** @var QuizStep $firstStep */ $firstStep = $quizSteps[0]; //We expect $this->assertNotEmpty($quizSteps); $this->assertCount(10, $quizSteps); //We expect if quiz is type normal and level variable questions depends of level: $this->assertEquals($firstStep->getQuestion(), $questions[$firstQuestionExpectedIndex]); } public function provideQuizDataAndFirstQuestionExpectedIndex(): array { return [ [[], 0], [['type' => QuizType::NORMAL, 'level' => '1'], 10], [['type' => QuizType::NORMAL, 'level' => '2'], 20] ]; } }
This is the Trait who generate fake question
<?php namespace App\Tests\Quiz; use App\Quiz\Question; use Doctrine\ORM\EntityManagerInterface; use Exception; use Faker\Generator; Trait QuestionLoremTrait{ /** * This function persist a aleatory generated question, you must flush after * @param Generator $faker * @param EntityManagerInterface $em * @return Question * @throws Exception */ public function persistLoremQuestion(Generator $faker, EntityManagerInterface $em): Question { $nbrOfProps = random_int(2,4); $answerPosition = random_int(0, $nbrOfProps - 1); $props = []; for ($i = 0; $i < $nbrOfProps; $i++){ $props[$i] = $faker->sentence ; } $question = new Question(); $question ->setSharedId(random_int(1, 2147483647)) ->setInfo($faker->paragraph(3)) ->setStatement($faker->sentence ."?") ->setProps($props) ->setAnswerPosition($answerPosition) ; $em->persist($question); return $question; } }
This is my Quiz class:
<?php namespace App\Quiz; use Symfony\Component\Config\Definition\Exception\Exception; class Quiz { /** * Quiz constructor. * @param QuizQuestionManagerInterface $quizQuestionManager * @param array $quizData * This array of key->value represent quiz properties. * Valid keys are 'step','level','type'. * You must use QuizType constant as type value * @param string $type * @param int $level * @param int $currentStep * @param array $steps */ public function __construct( private QuizQuestionManagerInterface $quizQuestionManager, private string $type = QuizType::FAST, private int $level = 0, private array $quizData = [], private int $currentStep = 0, private array $steps = []) { if ($quizData != []) { $this->hydrate($quizData); } $this->setSteps(); } private function hydrate(array $quizData) { foreach ($quizData as $key => $value) { $method = 'set' . ucfirst($key); // If the matching setter exists if (method_exists($this, $method) && $method != 'setQuestions') { // One calls the setter. $this->$method($value); } } } public function getCurrentStep(): int { return $this->currentStep; } public function getLevel(): int { return $this->level; } public function getType(): string { return $this->type; } public function getSteps(): array { return $this->steps; } private function setCurrentStep($value): void { $this->currentStep = $value; } private function setLevel(int $level): void { $this->level = $level; } private function setType($type): void { if (!QuizType::exist($type)) { throw new Exception("This quiz type didn't exist, you must use QuizType constante to define type", 400); } $this->type = $type; } private function setSteps() { $this->steps = []; $questions = $this->quizQuestionManager->getQuestions($this->type, $this->level); foreach ($questions as $question) { $this->steps[] = new QuizStep(question: $question); } } }
This is the Question class:
<?php namespace App\Quiz; use App\Repository\QuestionRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass=QuestionRepository::class) */ class Question { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private ?int $id; /** * @ORM\Column(type="integer") */ private ?int $sharedId; /** * @ORM\Column(type="string", length=1000, nullable=true) * @Assert\Length(max=1000) */ private ?string $info; /** * @ORM\Column(type="string", length=255, nullable=true) */ private ?string $statement; /** * @ORM\Column(type="array") */ private array $props = []; /** * @ORM\Column(type="integer") */ private ?int $answerPosition; public function getId(): ?int { return $this->id; } public function getSharedId(): ?int { return $this->sharedId; } public function setSharedId(int $sharedId): self { $this->sharedId = $sharedId; return $this; } public function getInfo(): ?string { return $this->info; } public function setInfo(?string $info): self { $this->info = $info; return $this; } public function getStatement(): ?string { return $this->statement; } public function setStatement(?string $statement): self { $this->statement = $statement; return $this; } public function getProps(): ?array { return $this->props; } public function setProps(array $props): self { $this->props = $props; return $this; } public function getAnswerPosition(): ?int { return $this->answerPosition; } public function setAnswerPosition(int $answerPosition): self { $this->answerPosition = $answerPosition; return $this; } }
If anyone understands this behavior. I thank him in advance for helping me sleep better :-)
-
PHPUnit: How do you Unit test for multiple if-else/factory?
I have a class
ParentIdResolver
which returns the parent id of the product based on the type.
The class looks like:<?php namespace App\Model; use App\Model\Product\Bundle; use App\Model\Product\Configurable; use App\Model\Product\Downloadable; class ParentIdResolver { /** * @var Bundle */ private $bundle; /** * @var Configurable */ private $configurable; /** * @var Downloadable */ private $downloadable; public function __construct( Bundle $bundle, Configurable $configurable, Downloadable $downloadable ) { $this->bundle = $bundle; $this->configurable = $configurable; $this->downloadable = $downloadable; } public function getParentId($productId, $productType) { $parentIds = []; if ($productType == 'bundle') { $parentIds = $this->bundle->getParentIdsByChild($productId); } elseif ($productType == 'configurable') { $parentIds = $this->configurable->getParentIdsByChild($productId); } elseif ($productType == '') { $parentIds = $this->downloadable->getParentIdsByChild($productId); } return $parentIds[0] ?? null; } }
And I am trying to test the
getParentId()
as:<?php namespace App\Test\Unit; use PHPUnit\Framework\TestCase; use App\Model\ParentIdResolver; use App\Model\Product\Bundle; use App\Model\Product\Configurable; use App\Model\Product\Downloadable; class ParentIdResolverTest extends TestCase { protected $model; protected $bundleMock; protected $configurableMock; protected $downloadableMock; public function setUp(): void { $this->bundleMock = $this->createPartialMock( Bundle::class, ['getParentIdsByChild'] ); $this->configurableMock = $this->createPartialMock( Configurable::class, ['getParentIdsByChild'] ); $this->downloadableMock = $this->createPartialMock( Downloadable::class, ['getParentIdsByChild'] ); $this->model = new ParentIdResolver( $this->bundleMock, $this->configurableMock, $this->downloadableMock ); } /** * @dataProvider getParentIdDataProvider */ public function testGetParentId($productId, $productType, $parentId) { if ($productType == 'bundle') { $this->bundleMock->expects($this->any()) ->method('getParentIdsByChild') ->willReturn([$parentId]); } if ($productType == 'configurable') { $this->configurableMock->expects($this->any()) ->method('getParentIdsByChild') ->willReturn([$parentId]); } if ($productType == 'downloadable') { $this->downloadableMock->expects($this->any()) ->method('getParentIdsByChild') ->willReturn([$parentId]); } $this->assertEquals($parentId, $this->model->getParentId($productId, $productType)); } public function getParentIdDataProvider() { return [ [1, 'bundle', 11], [2, 'configurable', 22], [3, 'downloadable', 33], ]; } }
And I don't feel that I am doing it correctly, maybe I need to refactor the main class?
Please suggest how would you refactor or write unit test in this case. -
Python and Raspberry Pi: Is it possible to write script to use TDD for uploading the "heartbeat"?
new programmer here. I have been working with python for almost a year and am now diving into Test Driven Development. My current project is writing python script to upload the "heartbeat" or pulse of the raspberry pi, along with photos and all other activities. Currently, this is what I have as my .py script:
import time import platform import signal import sys import pytest class SpiderCloud(object): def __init__(self): pass def upload_heartbeat(self, heartbeat): # upload the heartbeat to txt file and log print("HEARTBEAT: UPLOADING") pass def check_return_message(self, message): print(" ") pass def upload_photos(self, photos): # upload photos to log/file print("UPLOADING: PHOTO") pass def upload_activities(self, activities): # upload activities to log/file print("UPLOADING: ACTIVITY") pass if __name__=="__main__": pass
and for the test file:
import sys, pytest import src.spider_cloud as SC from src.spider_cloud import cloud from src.spider_cloud.cloud import SpiderCloud class TestSpiderCloud(object): def test__init__(self): assert SC.__init__ is True pass def test_upload_heartbeat(self): sys.path.append(cloud) imp = dir(SpiderCloud) if imp is True: assert SC.upload_heartbeat is True pass def test_check_return_message(self): assert SC.check_return_message is True pass def test_upload_photos(self): assert SC.upload_photos is True pass def test_upload_activities(self): assert SC.upload_activities is True pass if __name__=='__main__': pytest.main()
My issue I am running into is, I do not know how to start the heartbeat function. I know this may be rudimentary in terms of a question, but any help would be greatly appreciated on how to get going.
Thank you in advance.
-
Deploying platform-specific Maven snapshots from separate build jobs
I have a Maven project which outputs platform-specific artifacts for three platforms: Linux, Windows and MacOS. I have implemented this by putting the OS/arch combination into the classifier, i.e. win-amd64, mac-x86_64, linux-amd64.
The artifacts must be built on the platform they relate to, i.e. the Windows artifact can only be generated on a Windows machine and so on. So I have separate Jenkins jobs running the Maven build, one per platform. This all works fine for deployment and for downloading releases but unfortunately other build that want to consume snapshots of these artifacts cannot use a version like
1.0.0-SNAPSHOT
to access them. They have to use the full timestamp and build number.The problem is that because the builds have different build numbers and timestamps, the maven-metadata.xml gets rewritten on each deployment, and only the latest SNAPSHOT is referenced. It's basically a race where the slowest build wins. The top of the maven-metadata.xml in the repository looks like this:
<version>1.1.0-SNAPSHOT</version> <versioning> <snapshot> <timestamp>20210303.091119</timestamp> <buildNumber>18</buildNumber> </snapshot> <lastUpdated>20210303091134</lastUpdated> <snapshotVersions> <snapshotVersion> <classifier>linux-amd64</classifier> <extension>tar.gz</extension> <value>1.1.0-20210303.085348-16</value> <updated>20210303085348</updated> </snapshotVersion> <snapshotVersion> <classifier>win-amd64</classifier> <extension>zip</extension> <value>1.1.0-20210303.090821-17</value> <updated>20210303090821</updated> </snapshotVersion> <snapshotVersion> <classifier>mac-x86_64</classifier> <extension>tar.gz</extension> <value>1.1.0-20210303.091119-18</value> <updated>20210303091119</updated> </snapshotVersion> ...
The snapshot version refers to a timestamp and build number that only exists for the MacOS artifact. So I can fetch the artifact
<groupId>:<artifactId>:1.1.0-SNAPSHOT:mac-x86_64
but I cannot fetch<groupId>:<artifactId>:1.1.0-SNAPSHOT:linux-amd64
.The deployment repository is an Artifactory server.
I don't know if this is a problem with Artifactory, or the maven-deploy-plugin, or if I am just doing something stupid. Is there a better way to handle deployment of platform-specific artifacts from multiple builds?
-
AWS ElasticSearch - Automating manual snapshots
The requirement - A customer requires an automated mechanism that takes a manual snapshot of an AWS ElasticSearch domain (production) on a daily basis. The target of the snapshot is an AWS S3 bucket.
Expected flow
- Schedule Daily @ 2am --> start process --> take snapshot --> wait 5 min --> check snapshot status (success/in_progress/failed)
- if
state==IN_PROGRESS
, check snapshot status again, up to 10 times, interval of 5 minsstate==SUCCESS
- end process (success)state==IN_PROGRESS
- when reaching 10 retries (50 mins), end process (failed)state==FAILED
- end process (failed)
- If previous step failed, send push notification (Slack/Teams/Email/etc.)
Motivation - The automated snapshots that are taken by AWS can be used for disaster recovery or a failure in an upgrade, they cannot be used if someone by accident (yes, it happened) deleted the whole ElasticSearch cluster.
Haven't found an out-of-the-box Lambda/mechanism that meets the requirements. Suggestions? Thoughts?
p.s- I did a POC with AWS Step Functions + Lambda in VPC, which seems to be working, but I'd rather use a managed service or a living open-source project.
-
Socket.io 404 with AWS LAMP Stack Instance Created From a Working Snapshot Not Working
As in title, I created a snapshot of an AWS LAMP stack running a node.js server that works fine, then create another instance from that snapshot and get a 404 error for sockets.io. Been looking over the forums but can't seem to figure out what's wrong. The code is:
var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var crypto = require('crypto'); const util = require('util'); const expressip = require('express-ip'); app.use(expressip().getIpInfoMiddleware); ... http.listen(3000, function(){ ... });
Once the instance is created, I SSH into it and run the server JS script, which runs without errors.
Any idea what I'm doing wrong?
Here is a working example: http://52.62.156.187/demo.html
and the not working one: http://54.252.190.28/demo.html