Jest, data in one test bleeding to another - jestjs

I have been facing this issue with jest, where the data i set in one test is creeping into another test and causing it to fail.
Let me try to show some code
spec.ts
const MOCK_PRODUCT = require('../dummydata/dummydata.json');
describe('function 1', () => {
test('something', () => {
const productData = MOCK_PRODUCT;
expect(...) // works fine
});
test('something else', () => {
const productData = MOCK_PRODUCT;
productData.someField.push({...})
expect(...) // works fine
});
});
describe('function 2', () => {
test('something more', () => {
const productData = MOCK_PRODUCT;
expect(...) // fails
});
});
My test file goes like this. There is a describe block for each function and multiple tests inside the describe block.
What i observe is that, when i changed the productData in second test as you see above, the changed data is available in all the tests in the next describe block, causing them to fail.
Am i doing something wrong? is something missing?

It seems MOCK_PRODUCT is a object, then when you set const productData = MOCK_PRODUCT;, the productData is a referent to MOCK_PRODUCT. This means if you update producData content, MOCK_PRODUCT content also be updated.
In the previous test, you update productData object, then the next test will not working as your expected.
A simple way to avoid it is to stop using referent way, just clone to a new object for every test.
const productData = {...MOCK_PRODUCT};
My recommendation, define productData variable at the top level, assign cloned MOCK_PRODUCT for it in beforeEach. Then use productData in each test.

Unfortunately that is by design. The script is just run from top to bottom. beforeAll, beforeEach, afterEach and afterAll functions could help you to factor out some code from your test methods.
You can also use the cloneDeep function from lodash to make an completely independent copy of the test data before each test runs.
const MOCK_PRODUCT = require('../dummydata/dummydata.json');
const _ = require('lodash'),
let productData = undefined;
beforeEach(() => {
productData = _.cloneDeep(MOCK_PRODUCT);
})
describe('function 1', () => {
test('something', () => {
expect(...) // works fine
});
test('something else', () => {
productData.someField.push({...})
expect(...) // works fine
});
});
describe('function 2', () => {
test('something more', () => {
expect(...) // fails
});
});

Related

How to prevent Jest from running tests as globalSetup is still running?

My global setup code creates a dynamodb table and the global teardown destroys it. The code is just generic dynamodb.createTable() and dynamodb.deleteTable()
My basic test that just wants to return a list of inserted entities:
describe('Test suite', () => {
beforeAll(async () => {
await InsertEntity()
})
test('test', async () => {
const response = await getEntities()
expect(response.success).toBe(true)
})
})
My InsertEntities code:
export const InsertEntities = async () => {
const entity = new Entity({
// some data
})
const result = await entity.save()
console.log('entity', result)
}
But as soon as I run jest, it starts running the tests before the table is up, so it fails horribly. What can I do to stop this behavior?
What have I already tried: Running jest with --runInBand, adding jest.setTimeout() with various different timings going as up as 20secs. Changing the beforeAll to beforeEach. Checking how much time the table needs to go up: 10secs.
I've changed this code to run as setupFilesAfterEnv, but it still crashed. I've written similar code before and it still works! But not this time.
So, what am I missing from jest flow?

Vue component doing async hangs at await while jest testing

I'm trying to test a component that loads data asynchronously when mounted. The component works as expected, it's just the test that's giving me issues. The component's async loadData() function hangs at await axios.get() while jest test runner is in the component.vm.$nextTick(). As a result, the checks in the $nextTick loop never pass.
Immediately after the $nextTick loop times out, the component's await statement completes and the component renders itself. axios is mocked, so it should resolve really fast. If I remove the await and just fill in a constant instead, the entire thing executes as expected.
I'm guessing that $nextTick loop is not asynchronous and it's consuming the thread, even though this is the recommended way of testing asynchronous stuff. The problem is, I don't have an onclick async handler to await: this method is called from onMount.
Unfortunately, I don't know how to make a jsFiddle of this one, so I hope this will be enough:
my component (the relevant parts)
export default {
data() { return { content: '' }; },
mounted() { this.loadDoc() }
methods: {
async loadDoc() {
const res = await axios.get('some url'); // <-- this is the line that hangs until timeout
// const res = { data: 'test data'}; // this would test just fine
this.content = res.data;
}
}
}
and my component.spec.js:
jest.mock('axios', () => ({
get: async (url) => {
return { data: 'test data' };
}
};
describe('my super test', () => {
it('renders', (done) => {
const doc = shallowMount(myComponent);
doc.vm.$nextTick(() => {
expect(doc.html()).toContain('test data'); // <-- this never matches
done();
});
});
});
I would delete, but I just spent quite some hours for something that was suggested in the docs, but not explained that it's the only way... I'm hoping somebody else finds this useful.
Using flush-promises package instead of $nextTick loop immediately "fixed" the problem
Code sample (rework of above):
describe('my super test', () => {
it('renders', async() => {
const doc = shallowMount(myComponent);
await flushPromises();
expect(doc.html()).toContain('test data'); // <-- now it works
});
});

How to use jest.each asynchronously

I am having problems loading filenames into jest.each asynchronously.
My code:
let files: string[][]
function getFilesWorking() {
files = [["test1"], ["test2"]]
}
async function getFilesAsync() {
files = await Promise.resolve([["test1"], ["test2"]])
}
beforeAll(() => {
console.log("before")
})
describe.only("Name of the group", () => {
getFilesAsync()
test.each(files)("runs", f => {})
})
beforeAll is executed before each test but NOT before initialization of test.each, so I end up with undefined.
How can I load files before using test.each?
You can pass an async callback to beforeAll and await getFilesAsync within it
beforeAll(async () => {
await getFilesAsync();
})
As of Jest 28.1.3 and prior, this is not possible. There is an open issue documenting this behavior.
The best thing you can do for now is put your tests in a regular it() test and do a deep value comparison:
it('tests an array of cases', async () => {
const data = await getSomeAsyncData()
const expectedData = [ ... ]
expect(data).toEqual(expectedData)
})
You can use beforeEach to set up code that will run prior to tests for any given scope, https://jestjs.io/docs/setup-teardown:
beforeEach(() => {
console.log('before every test');
});
describe.only(('Name of the group') => {
beforeEach(() => {
console.log('before tests in this describe block');
})
})
Jest is only going to run the tests in your describe.only block. If you want to use beforeEach in other blocks and run those tests as well, change describe.only to describe.
(Edit: I know this is a year late, I'm just trying to look for a similar problem/solution set and thought I could answer this.)

Disable a single nock scope immediately to re-setup a mocked URL

Using nock, is there a way to disable a single nock scope?
I've been struggling with some tests that set up nocks of the same URL as some other tests. They both run fine separately, but when run in the same mocha session one of them fails, because I'm unable to re-nock the active nock scopes, meaning the nocks that were set up catches all the requests.
What I've tried:
If I set up some nocks in before() and then call scope.persist(false) in my after(), it only "unpersists" the scope, so that it's active for one more request. It does not immediately disable it.
I've found that nock.cleanAll() immediately disables the nocks so that they can be set up again, but then it also disables any global nocks that may have been set up once, common to all test cases.
So far, the only solutions I've found are 1) use unique URL:s for all nocks, which isn't always possible or 2) use nock.cleanAll() and don't rely on any global nocks - instead make sure to only set up nocks in local before() functions, including setting up the global ones repeatedly for every test that needs them.
It seems it would be highly useful to be able to do
scope = nock('http://somewhere.com').persist().get('/'.reply(200, 'foo');
and then use that nock in a bunch of tests, and finally do
scope.remove();
However, I've not been able to do something like this. Is it possible?
Example:
before(async () => {
nock('http://common').persist().get('/').reply(200, 'common');
});
after(async () => {
});
describe('Foo tests', () => {
let scope;
before(async () => {
scope = nock('http://mocked').persist().get('/').reply(200, 'foo');
});
after(() => {
// scope.persist(false); // This causes the Bar tests to use the Foo nocks one more time :(
// nock.cleanAll(); // This also disables the common nocks
});
it('Should get FOO', async () => {
expect(await fetch('http://mocked').then(res => res.text())).to.equal('foo');
expect(await fetch('http://common').then(res => res.text())).to.equal('common');
});
it('Should get FOO again', async () => {
expect(await fetch('http://mocked').then(res => res.text())).to.equal('foo');
expect(await fetch('http://common').then(res => res.text())).to.equal('common');
});
});
describe('Bar tests', () => {
let scope;
before(async () => {
scope = nock('http://mocked').persist().get('/').reply(200, 'bar');
});
after(() => {
// scope.persist(false);
// nock.cleanAll();
});
it('Should get BAR', async () => {
expect(await fetch('http://mocked').then(res => res.text())).to.equal('bar');
expect(await fetch('http://common').then(res => res.text())).to.equal('common');
});
it('Should get BAR again', async () => {
expect(await fetch('http://mocked').then(res => res.text())).to.equal('bar');
expect(await fetch('http://common').then(res => res.text())).to.equal('common');
});
});
These tests either fail the 3rd test if using scope.persist(false) (since that test still gets the foo version), or fails tests 3 and 4 if using nock.cleanAll(), since the common nocks are then removed.
I also had this issue and found a way to work around it by listening to the request event emitted by the scope and removing the interceptor when the event is fired. Ideally, I think you should be listening to the replied event but for some reason, that event wasn't firing when I tried it, not sure why. But the code below worked for me:
/**
* #jest-environment node
*/
const nock = require('nock');
describe('Test suite', () => {
test('Test case', async () => {
let interceptor1 = nock('https://example-url.com', {
reqHeaders: {
'Content-Type': 'text/xml',
soapaction: 'http://www.sample.com/servie/getOrders',
},
})
.post('/');
let interceptor2 = nock('https://example-url.com', {
reqHeaders: {
soapaction: 'http://www.sample.com/servie/getProducts',
},
})
.post('/');
let scope = interceptor1.replyWithFile(200, path.join(__dirname, './path1.xml'));
interceptor2.replyWithFile(200, path.join(__dirname, './path.xml'));
scope.on('request', (req, interceptor) => {
nock.removeInterceptor(interceptor1);
});
const resp = await asynccall();
expect(resp).toStrictEqual(exp);
});
});
As described here: Unable to remove interceptors using nock I found a way of not storing the interceptors by just setting the mock again (which apparently returns the interceptor again) and then using the returned interceptor in the removeInterceptor() function. This returns true indeed and does work in my tests.
I found a pretty simple workaround for this one - the scope has a property called "interceptors", which is an array of the various interceptors the scope uses. You can replace the "body" property of the interceptor with whatever you want.
let scope = nock(base).get(path).reply(200, []).persist();
testable.call(path).then(console.log); //returns []
scope.interceptors[0].body = [1,2,3];
testable.call(path).then(console.log); //returns [1,2,3]

Mocha tests with async initialization code

I am writing tests for a REST client library which has to "login" against the service using the OAuth exchange. In order to prevent logging in for every endpoint I am going to test I'd like to write some sort of "test setup" but I am not sure how I am supposed to do this.
My test project structure:
test
endpoint-category1.spec.ts
endpoint-category2.spec.ts
If I had only one "endpoint category" I had something like this:
describe('Endpoint category 1', () => {
let api: Client = null;
before(() => {
api = new Client(credentials);
});
it('should successfully login using the test credentials', async () => {
await api.login();
});
it('should return xyz\'s profile', async () => {
const r: Lookup = await api.lookup('xyz');
expect(r).to.be.an('object');
});
});
My Question:
Since the login() method is the first test there, it would work and the client instance is available for all the following tests as well. However, how can I do some sort of setup where I make the "logged in api instance" available to my other test files?
Common code should be moved to beforeEach:
beforeEach(async () => {
await api.login();
});
At this point should successfully login using the test credentials doesn't make much sense because it doesn't assert anything.
describe('Endpoint category 1', () => {
let api: Client = null;
beforeEach(() => {
api = new Client(credentials);
});
afterEach(() => {
// You should make every single test to be ran in a clean environment.
// So do some jobs here, to clean all data created by previous tests.
});
it('should successfully login using the test credentials', async () => {
const ret = await api.login();
// Do some assert for `ret`.
});
context('the other tests', () => {
beforeEach(() => api.login());
it('should return xyz\'s profile', async () => {
const r: Lookup = await api.lookup('xyz');
expect(r).to.be.an('object');
});
});
});
Have you had a look at https://mochajs.org/#asynchronous-code ?
You can put in a done-parameter in your test functions and you will get a callback with this you have to call.
done() or done(error/exception)
This done would be also available in before and after.
When calling done() mocha knows your async-code has finished.
Ah. And if you want to test for login, you shouldn't provide this connection to other tests, because there is no guarantee of test order in default configuration.
Just test for login and logout afterwards.
If you need more tests with "login-session", describe a new one with befores.

Resources