Jest - How do I reset object state for each test? - javascript

I am new to Jest, and I am trying to figure out how to to reset the test object after each test.
Current Code
describe.only('POST request - missing entry', () => {
// newBlog is the "test" object
let newBlog = {
title: 'Test Title',
author: 'Foo Bar',
url: 'www.google.com',
likes: 100
}
test('sets "likes" field to 0 when missing', async () => {
delete newBlog.likes // propagates to next test
console.log(newBlog)
})
test('returns 400 error when "title" and "url" fields are missing', async () => {
console.log(newBlog)
})
})
Objective: I am writing test using jest to test for bad POST request. i.e. my POST request will intentionally have missing fields for each test.
likes field will be omitted from first test while title, url field will be missing from second test. Goal is to write newBlog object only once rather than rewriting objects for each tests.
Problem Main issue here is that the result of first test propagates to next test, i.e. when removing likes field for first test, it stays like that and starts the second test without having likes field.
I want to know how I can reset the content of object for each test.
Attempts So far, I tried few things:
I used BeforeEach to reset the newBlog in following manner:
beforeEach(() => {
let newBlog = {
title: 'Test Title',
author: 'Foo Bar',
url: 'www.google.com',
likes: 100
}
return newBlog
})
However, above code does not work since newBlog is in different scope so each test does not recognize newBlog variable.
I also used AfterEach to reset in following manner:
afterEach(() => {
jest.clearAllMocks()
})
This time, it ran but gave me the same results as first code snippet.
I would like to know how to reset objects for each test as many of the solution discussed in stackoverflow seems to focus on resetting functions rather than objects.
Thank you for your help in advance.

Try something like this, declare the variable in the describe and reset it in the beforeEach:
describe.only('POST request - missing entry', () => {
// newBlog is the "test" object
let newBlog;
beforeEach(() => {
newBlog = {
title: 'Test Title',
author: 'Foo Bar',
url: 'www.google.com',
likes: 100
}
});
test('sets "likes" field to 0 when missing', async () => {
delete newBlog.likes // propagates to next test
console.log(newBlog)
})
test('returns 400 error when "title" and "url" fields are missing', async () => {
console.log(newBlog)
})
})

You were correct in needing to use Jest's beforeEach() function; however, the only things that are returnable from beforeEach() are promises and generators—returning newBlog from beforeEach() does nothing. What I would do is create a local variable in the describe function and have beforeEach() reassign that variable before each test runs as seen below.
fdescribe('POST request - missing entry', () => {
let newBlog;
beforeEach(() => {
newBlog = {
title: 'Test Title',
author: 'Foo Bar',
url: 'www.google.com',
likes: 100
}
});
test('sets "likes" field to 0 when missing', async () => {
delete newBlog.likes; // remove likes key from copied object
console.log(newBlog);
});
test('returns 400 error when "title" and "url" fields are missing', async () => {
delete newBlog.title;
delete newBlog.url;
console.log(newBlog);
});
})
Additionally, jest.clearAllMocks() clears all calls and instances of a mocked function which will not reset the newBlog variable in the way you want to use it here.

I usually deep copy the test object in this kind of scenarios. So I have a helper function (you can use e.g. Lodash's deepClone function too)
const deepCopy = (obj: Object) => JSON.parse(JSON.stringify(obj));
In your tests you create a new deep copy of the wanted test object instead of mutating it's state like this:
test('sets "likes" field to 0 when missing', async () => {
let testObj = deepCopy(newBlog)
delete testObj.likes // propagates to next test
console.log(testObj)
})

Related

Best way to chain nested loops of mutations in React-Query?

Let's say I have the following piece of data, a list of puzzle objects, where each object has a puzzle_name field and a list of patterns within it:
const puzzles = [
{
puzzle_name: 'Test Data 1',
patterns: [
{pattern_name: 'test', type: 'test type', hours: 12},
{pattern_name: 'test 2', type: 'test type 2', hours: 22}
]
},
{
puzzle_name: 'Test Data 2',
patterns: [
{pattern_name: 'test', type: 'test type', hours: 12},
{pattern_name: 'test 2', type: 'test type 2', hours: 22},
{pattern_name: 'test 3', type: 'test type 3', hours: 33}
]
}
];
The goal here is to create each of the puzzles first (using just their names), get their ID once created, and create each individual pattern using the puzzle's ID. So I need to talk to two POST routes for this list of data. The first one takes a single object with the pattern_name field, so -
POST /puzzle takes {puzzle_name: string} as the body and returns {puzzle_name: string, puzzle_id: string}
The second route needs this puzzle_id in its param and a single pattern object at a time (from the patterns list above) -
So that would be POST /my-app.com/{puzzleId}/pattern, which takes a pattern object as its body and it returns the newly created object, which we don't need to worry about for the sake of this example.
Now for my attempt at this, I have the following two mutations -
const createPuzzleMutation = useMutation(({body}) => createPuzzle(body));
const createPatternMutation = useMutation(({puzzleId, body}) => createPattern(puzzleId, body));
And then I'm iterating through puzzles and creating a list of promises for the createPuzzleMutation. In the onSuccess function for each puzzle, I'm then going through the corresponding patterns list and calling the createPatternMutation using the ID of the puzzle, like so -
const puzzlesMutations = puzzles.map((puzzle) =>
createPuzzleMutation.mutateAsync(
{body: {name: puzzle.puzzle_name}},
{
onSuccess: async (data) => {
const patternsMutations = puzzle.patterns.map((pattern) => {
return createPatternMutation.mutateAsync(
{
puzzle_id: data.data.id,
body: {
...pattern
}
}
);
});
// waiting for all patterns to be created under a particular puzzle
await Promise.allSettled(patternsMutations);
}
}
)
);
// waiting for an individual puzzle to be created
await Promise.allSettled(puzzlesMutations);
It's creating both the puzzle names just fine, so it's talking to the first route, the POST /puzzle route just fine and getting the ID's for both the puzzles. But it's only calling the second route (POST /my-app.com/{puzzleId}/pattern) for the patterns list in the second puzzle. It's not calling the second POST route for the patterns array in the first puzzle.
I saw in the docs that for consecutive mutations, they'll be fired only once. Is this constraint related to my example here? Or am I missing something? Would appreciate any help.
I saw in the docs that for consecutive mutations, they'll be fired only once. Is this constraint related to my example here?
yes it is. If you want a callback that fires for every invocation of a mutation, you need to use the onSuccess callback of useMutation itself, not the callback on mutateAsync. There, you can also return a Promise, and react-query will await it before marking the mutation as "finished".
const createPuzzleMutation = useMutation(
({body}) => createPuzzle(body),
{
onSuccess: async (data) => {
const patternsMutations = puzzle.patterns.map((pattern) => {
return createPatternMutation.mutateAsync(
{
puzzle_id: data.data.id,
body: {
...pattern
}
}
);
});
// waiting for all patterns to be created under a particular puzzle
return Promise.allSettled(patternsMutations);
}
}
}
);

How do I increment automatically a variable when calling a Cypress method?

createUserPatch is an API custom command to create a new User.
You can see that I have created a variable "A" inside it.
The variable is used in the body emails part [a]+'freddie.doe#example.com','type': 'work','primary': true}]
I want to find a way to automatically increase the variable "A" whenever I call the command createUserPatch.
Cypress.Commands.add('createUserPatch', (user) => {
var A = 1;
cy.request({
method: 'POST',
url: '/scim/v2/users',
qs: {
key : Cypress.env('apiKey')
},
body :{
schemas: user.schemas,
userName: user.userName,
emails: [{ 'value': [A]+'freddie.doe#example.com','type': 'work','primary': true}],
name : 'Maurice'
}
}).then(res => {
return res.body;
});
});
I use this command in the test below in a before each.
let user = {
schemas:'["urn:ietf:params:scim:schemas:core:2.0:User"]',
userName: 'John',
userId: null,
groupID: null
};
describe('Deactivating a user ', () => {
beforeEach(() => {
cy.createUserPatch(user).then((newUser) => {
return user.userId = newUser.id;
});
});
....
Each time I run this test.
I want the value of the email to be increased.
First test -> 0freddie.doe#example.com
Second test -> 1freddie.doe#example.com
Third test -> 2freddie.doe#example.com
etc...
Cypress clears variables between tests, but a few ways to preserve data have been suggested in other questions (e.g write data to file).
Since you already use Cypress.env(name) to access environment variables you could use Cypress.env(name, value) to track the prefix.
Cypress.Commands.add('createUserPatch', (user) => {
let prefix = Cypress.env('emailprefix') || 0; // first time it may be undefined
// so "|| 0" initializes it
Cypress.env('emailprefix', ++prefix); // increment and save
cy.request({
...
emails: [{ 'value': prefix+'freddie.doe#example.com','type': 'work','primary': true}],
Note, the prefix value will be preserved between runs, which may or may not be what you want.
To clear it between runs, add a before() which resets the value
before(() => Cypress.env('emailprefix', 0) );
beforeEach(() => {
cy.createUserPatch().then(console.log)
})

How to partially test shape and partially test values of a json object

I would like to test the shape of my json in my mocha expectations. Some things I know like 'name' but others(_id) come from the database. For them, I only care that they are set with the proper type.
Here I have my expectation:
expect(object).to.eql({
recipe:
{
_id: '5fa5503a1fa816347f3c93fe',
name: 'thing',
usedIngredients: []
}
})
I would rather do something like this if possible:
expect(object).to.eql({
recipe:
{
_id: is.a('string'),
name: 'thing',
usedIngredients: []
}
})
Does anyone know of a way to accomplish this? Or is it best to just break this up into multiple tests?
You can do this by using chai-json-pattern plugin.
Chai JSON pattern allows you to create blueprints for JavaScript objects to ensure validation of key information. It enables you to use JSON syntax extends with easy to use validators. It came up mostly for testing API with cucumber-js but can be used in any application. Additionally, you can extend base functionality with custom validators
E.g.
const chai = require('chai');
const chaiJsonPattern = require('chai-json-pattern').default;
chai.use(chaiJsonPattern);
const { expect } = chai;
describe('64715893', () => {
it('should pass', () => {
const object = {
recipe: {
_id: Math.random().toString(),
name: 'thing',
usedIngredients: [Math.random() + 'whatever'],
},
};
expect(object).to.matchPattern(`{
"recipe": {
"_id": String,
"name": "thing",
"usedIngredients": Array,
},
}`);
});
});
test result:
64715893
âś“ should pass
1 passing (50ms)

Run tests multiple times with different beforeEach

So I have these 2 cases in my tests. First one works fine, in the second one I try to extract the beforeEach declaration outside and it fails but I don't understand why. This is a simple case, basically I try to define an array and make a loop on that in order to run the tests multimple time with different beforeEach params declaration.
CASE 1
var params;
describe('When initializing', function () {
beforeEach(function () {
params = {
name: 'test 1'
};
});
it('should ..', function () {
params.name = 'test 2';
expect(...); => success
});
it('should ..', function () {
expect(...); => success because it expects params.name to be 'test 1' and it is 'test 1'
});
});
CASE 2
var params;
var test = {
name: 'test 1'
};
describe('When initializing', function () {
beforeEach(function () {
params = test;
});
it('should ..', function () {
params.name = 'test 2';
expect(...); => success
});
it('should ..', function () {
expect(...); => fails because it expects params.name to be 'test 1' and it is 'test 2'
});
});
In the second test if I console.log(test.name) inside the describe I will get test 2, somehow it got overriden even though the previous it did just params.name = 'test 2'; and not test.name = 'test 2';
The difference is that in case 1 you're creating a new object every time beforeEach is called, while in case 2 you're not.
Combined with that is the fact that your first test mutates the object. If all the tests are referring to the same object (ie, case 2) then that mutation will affect any code that runs after the first test. If instead the object is overwritten before each test (case 1), then the mutation won't affect other tests.
There are a few options for how to address this. One is to just to keep case 1; by resetting to a known state each time, you can have a clean state for all the tests to work off of. Another option is to not mutate the object. Perhaps the tests could copy the object and then modify that copy.

Include order is relevant on scope but not on finder options

Depending on the order I place the includes in the scope, sequelize won't fetch one of the includes I requested. Oddly, if instead of a scope I put the options directly in the finder options (findOne() in this case), both requests work correctly. Why is this happening?
const Sequelize = require('sequelize');
const sequelize = new Sequelize({ dialect: 'sqlite', storage: 'db.sqlite' });
const Foo = sequelize.define('foo', { name: Sequelize.STRING });
const Bar = sequelize.define('bar', { name: Sequelize.STRING });
Foo.belongsToMany(Bar, { through: 'foo_bars', foreignKey: 'fooId' });
Foo.belongsTo(Bar, { foreignKey: 'barId', as: 'whatever' });
const includeOrder1 = { include: [{ model: Bar, as: 'whatever' }, Bar] };
const includeOrder2 = { include: [Bar, { model: Bar, as: 'whatever' }] };
Foo.addScope('test1', includeOrder1);
Foo.addScope('test2', includeOrder2);
const logGotWhatever = obj => console.log('Got whatever: ' + !!obj.whatever);
sequelize.sync()
.then(() => Bar.create({ name: 'The Bar' }).then(bar => {
return Foo.create({ name: 'The Foo', barId: bar.id }).then(foo => foo.addBar(bar));
}))
.then(() => Foo.findOne(includeOrder1).then(logGotWhatever))
.then(() => Foo.findOne(includeOrder2).then(logGotWhatever))
.then(() => Foo.scope('test1').findOne().then(logGotWhatever))
.then(() => Foo.scope('test2').findOne().then(logGotWhatever));
After running npm install sequelize sqlite3, the code above outputs:
Got whatever: true
Got whatever: true
Got whatever: false
Got whatever: true
Although I expected true in all four cases.
I'm using the most recent (non-beta) version of sequelize at the moment: 4.42.0
This is indeed a bug, which was indirectly resolved by PR #9735 in 2018-10-28, which changed how includes are dealt with (both in scopes and in finder options) and is available in v5.0.0-beta.14 and above.
Running the code above with npm install sequelize#next sqlite3 yields:
Got whatever: true
Got whatever: true
Got whatever: true
Got whatever: true
As it should.
This fix probably will not be backported to v4 because it involved breaking changes on how includes work (although it isn't exactly a catastrophic change, is is technically a breaking change).

Categories

Resources