Handle async imports in jest.mock factory - node.js

I am reusing db mocks in different tests so I created aka factories for mocking a single table and calling them in jest.mock()
jest.mock('db', () => {
const {
UsersMockFactory,
RequestMockFactory
} = jest.requireActual('../../../../mocks');
return {
Users: UsersMockFactory(),
Requests: RequestMockFactory(),
};
});
The problem is that 19 tests will pass with mock from that file but 20th will throw an error RequestMockFactory is not a function.
I've tried using const mocks = require('../../../../mocks') and then console.log the result but RequestMockFactory still wasn't there for some reasons. I don't know why but it is the main reason I have this problem. I've tried to use await import() like this and it imports correctly:
jest.mock('db', async () => {
const {
UsersMockFactory,
RequestMockFactory,
} = await import('../../../../mocks');
return {
Users: UsersMockFactory(),
Requests: RequestMockFactory(),
};
});
Now the problem is that mock factory returns a Promise for an entire mock and obviously methods on a model are undefined.
I've tried to mock inside describe() , beforeAll(), inside it() - jest.mock is not working there.
How to handle imports in jest.mock without such pain?

Related

How to persist() when using nockNack with jest and Node?

I am currently working on some unit tests for an express app.
I am using "jest": "^29.4.1", "nock": "^13.3.0",.
The tests I am writing use nockBack.
Imagine I have 3 separate test files that run the code below. The first 2 properly run, save a nock fixture in the proper directory and then re-run just fine. As soon as I introduce a 3rd test; it runs and passes the first time (and saves a fixture etc...) but if I re-run the 3rd test it fails with this error error: Error [NetworkingError]: Nock: No match for request.... I read in the docs that a way to alleviate this is to use the persist() method BUT this is not documented for nockBack only for methods using nock to make calls to pseudo endpoints. I am testing 3rd party api calls that need to go out initially on the netowrk and then subsequent calls will be pulled from the fixtures.
I tried clearing interceptor use by adding these to all my tests:
beforeEach(() => nock.cleanAll());
afterEach(() => nock.cleanAll());
But this does not help to make the 3rd test pass when re-running.
I also tried adding persist() like so: const { nockDone } = await nockBack('post-data.json').persist(); <---- but this fails since it's not a recognized method.
Is there a way to make this work when using nockBack?
Test 1
const nockBack = require('nock').back;
const path = require('path');
const { getPosts } = require('./post');
nockBack.fixtures = path.join(__dirname, '__nock-fixtures__');
nockBack.setMode('record');
test('return a list of posts by a user', async () => {
const userId = 1;
const { nockDone } = await nockBack('post-data.json');
const data = await getPosts(userId);
expect(data.length).toBeGreaterThan(0);
data.forEach((post) => {
expect(post).toEqual(
expect.objectContaining({
userId,
})
);
});
nockDone();
});

JEST Matcher error: received value must be a mock function

I am receiving "Matcher error: received value must be a mock function" trying to compare the result of the completed function. I do have 1 mock statement that mocks 2 methods from the utilities module for the index module to complete successfully when referencing those. I assume, though, that I do not need to mock index modules in order to pass the test. Here's the whole test that's not working:
import * as index from '../src/index';
jest.mock('../src/utils', () => {
const originalModule = jest.requireActual('../src/utils');
return {
__esModule: true,
...originalModule,
downloadModule: jest.fn(() => 'downloaded'),
compareChecksum: jest.fn(() => true)
}
});
describe('testing ingex file', () => {
test('testing case with mocked functions', async () => {
await expect(index.execute()).resolves.toHaveReturnedWith(undefined);
});
});
Utils is just a file with series of useful methods used in index. While the function called in index looks like this (Redacted):
//src/index
export async function execute() {
switch(type) { // comes from env vars
case 'file': {
await downloadModule(params); // mocked to succeed
if (await compareChecksum(otherParams)) {// mocked to succeed
console.log("Equal");
return;
}
...
}
...
}
}
The complete error I am getting:
expect(received).resolves.toHaveReturnedWith(expected)
Matcher error: received value must be a mock function
Received has value: undefined
So index module is dependent on utils and I mocked all necessary methods for it to pass successfully. Received value just cannot be a mock function ever, and it does receive the "undefined" result as expected but refuses to compare it properly. Not sure why the expected result is supposed to be a mock function in this case.
In case it matters this is typescript (not clearly visible from the code provided) and ts-jest is installed.

How can I wrap a Mocha or Jest test suite in a Node.js AsyncLocalStorage.run?

I have a test suite for a Node.js+express.js application that requires database access using Sequelize. My previous experience was in Python+Django. In Django, there is a nice TestCase class that wraps each test in a transaction; the transaction is rolled back automatically at the end of each test. This is a good idea to ensure data-wise isolation among tests.
I want to replicate the same effect using Mocha or Jest. I get a feeling that there should be a way using Node.js (v16)'s AsyncLocalStorage, but I just can't get it to work.
My guess at the gist of it is something like this:
instantiate an AsyncLocalStorage in a module, and export it.
in my test file, import the AsyncLocalStorage instance;
_ call Mocha's describe();
inside the callback for describe, create a Sequelize transaction;
inside the callback for the Sequelize transaction:
store the transaction in the AsyncLocalStorage instance
call Mocha's before hook; then, inside the callback for the before hook:
create a Sequelize transaction checkpoint
insert some fixture rows into the database using that checkpoint
call Mocha's after book; then, inside the callback for the after hook:
rollback the checkpoint
call Mocha's it a couple of times to define each test; inside each test:
call some application functions that I want to test; each of these functions will:
import the AsyncLocalStorage instance and try to find a transaction in it
if a transaction is found in AsyncLocalStorage, create another checkpoint on it and use it
otherwise start a new transaction
Doing so would allow the application functions to see the fixture data that I inserted in Mocha's before hook. And then the checkpoint rollback in Mocha's after hook would just clean everything up. I wouldn't have to write my own SQL DELETE queries, for example, and clean up would always be correct. I wouldn't have to worry about one test suite accidentally leaving data that contaminates the next test suite (which would cause a lot of head scratching).
I tried two different ways, but none of them worked (the tests wouldn't actually get run).
Attempt 1:
import { QueryTypes, Transaction } from 'sequelize';
import { applicationFunction1, applicationFunction2 } from '../src/blabla';
import { sequelize, transactionStorage } from '../src/infrastructure/orm';
import { insertFixtureSQL } from './fixture'
const map = new Map<string, Transaction>();
describe('My super awesome test suite', async function() {
// eslint-disable-next-line consistent-this,babel/no-invalid-this
const mocha = this;
// `transactionStorage` is an AsyncLocalStorage instance
// start an AsyncLocalStorage run by giving it the map;
// other async functions down the chain should be able to retrieve the same map;
await transactionStorage.run(map, async () => {
// `sequelize` is a `Sequelize` instance
await sequelize.transaction(async (transaction) => {
// put the transaction in the map
map.set('transaction', transaction);
mocha.beforeEach(async () => {
// Create a checkpoint
const checkpoint = await sequelize.transaction({ transaction });
// Replace the transaction with the checkpoint (which itself is a sequelize Transaction instance)
map.set('transaction', checkpoint);
// Insert fixture data using the checkpoint;
// `insertFixtureSQL` is a string that contains an SQL INSERT statement.
await sequelize.query(insertFixtureSQL, { type: QueryTypes.INSERT, raw: true, transaction: checkpoint });
});
mocha.afterEach(async () => {
const checkpoint = map.get('transaction') as Transaction;
// If I just rollback the checkpoint, everything shall be cleaned up
await checkpoint.rollback();
});
it('My awesome test 1', async () => {
// inside `applicationFunction1()`, it will try to find the transaction from the
// `transactionStorage`;
// if found, it will create another checkpoint on it and use it;
// otherwise, it will create a new Sequelize transaction.
await applicationFunction1();
});
it('My awesome test 2', async () => {
// inside `applicationFunction2()`, it will try to find the transaction from the
// `transactionStorage`;
// if found, it will create another checkpoint on it and use it;
// otherwise, it will create a new Sequelize transaction.
await applicationFunction2();
});
});
});
});
Attempt 2:
import { QueryTypes, Transaction } from 'sequelize';
import { applicationFunction1, applicationFunction2 } from '../src/blabla';
import { sequelize, transactionStorage } from '../src/infrastructure/orm';
import { insertFixtureSQL } from './fixture'
const map = new Map<string, Transaction>();
// `transactionStorage` is an AsyncLocalStorage instance
// start an AsyncLocalStorage run by giving it the map;
// other async functions down the chain should be able to retrieve the same map;
transactionStorage.run(map, async () => {
// `sequelize` is a `Sequelize` instance
await sequelize.transaction(async (transaction) => {
// put the transaction in the map
map.set('transaction', transaction);
describe('My super awesome test suite', async function() {
this.beforeEach(async () => {
// Create a checkpoint
const checkpoint = await sequelize.transaction({ transaction });
// Replace the transaction with the checkpoint (which itself is a sequelize Transaction instance)
map.set('transaction', checkpoint);
// Insert fixture data using the checkpoint;
// `insertFixtureSQL` is a string that contains an SQL INSERT statement.
await sequelize.query(insertFixtureSQL, { type: QueryTypes.INSERT, raw: true, transaction: checkpoint });
});
this.afterEach(async () => {
const checkpoint = map.get('transaction') as Transaction;
// If I just rollback the checkpoint, everything shall be cleaned up
await checkpoint.rollback();
});
it('My awesome test 1', async () => {
// inside `applicationFunction1()`, it will try to find the transaction from the
// `transactionStorage`;
// if found, it will create another checkpoint on it and use it;
// otherwise, it will create a new Sequelize transaction.
await applicationFunction1();
});
it('My awesome test 2', async () => {
// inside `applicationFunction2()`, it will try to find the transaction from the
// `transactionStorage`;
// if found, it will create another checkpoint on it and use it;
// otherwise, it will create a new Sequelize transaction.
await applicationFunction2();
});
});
});
});
The difference between the two attempts is the order of the wrapping:
Attempt 1:
Mocha's describe()
transactionStorage.run()
sequelize.transaction()
Mocha's beforeEach/afterEach/it
Attempt 2:
transactionStorage.run()
sequelize.transaction()
Mocha's describe()
Mocha's beforeEach/afterEach/it
What happens when I try to run my test suite:
0 passing (1ms)
info: Executing (55fc411e-2a51-4095-9279-aec3942dc818): START TRANSACTION;
info: Executing (55fc411e-2a51-4095-9279-aec3942dc818): COMMIT;
Which means it didn’t actually find any test to run.
I think what's happening in your case is:
Test Suite starts and calls your wrappers
Your wrappers start connecting to database but wont finish in current loop tick, they are async
At the end of loop tick Test Suite doesn't see any tests and quits
The problem is asynchronous initialization of your tests. I'm not sure if it is possible to create tests asynchronously.
I would put asynchronous preparations to beforeAll.
And if you need to run tests inside of AsyncLocalStorage.run use a mock.
I've just faced similar problem and solved it with dependency injection of MockAsyncLocalStorage.
My code uses AsyncLocalStorage and I provide it slightly modified as if everything is inside AsyncLocalStorage.run
class MockAsyncLocalStorage extends AsyncLocalStorage<object> {
private globalStore = {};
getStore(): object {
const store = super.getStore();
if (!store) {
return this.globalStore;
}
return store
}
}
I use Nestjs so dependency injection in tests looks like this:
const moduleFixture = await Test.createTestingModule({
imports: [App],
})
.overrideProvider("AsyncLocalStorage")
.useValue(new MockAsyncLocalStorage())
.compile();

How to do callback in our component using react jest test cases

How can we do callback on success and failue cases for below lines of code for test coverage using jest
const handleService = () => {
window.domain.service("1321",'',onSuccess, onFailure)
}
const onSuccess = () => {
....update state values
}
const onFailure = () => {
....update state values
}
Something like this:
Spy on window.domain.service to gain access to the calls it receives. This will allow you to access the parameters of those calls which will be "1321",'',onSuccess, onFailure
Assign the function you wish to test to a variable
Invoke the function to execute the code in it (this will get you the coverage)
(Optional) assert that the callback functions behave correctly
Here is a snippet to help demonstrate
it('should run', () => {
// Some setup to create the function on the window, may not be needed if done elsewhere.
// Could be good to do this in a beforeEach and clean up in afterEach to avoid contaminating the window object
window.domain = {
service: () => {},
}
// Spy on the window.domain.service method.
// Provide a mock implementation if you don't want the real one to be called
const serviceSpy = jest.spyOn(window.domain, 'service');
executeYourCode();
// capture the arguments to the call
const [_arg1, _arg2, onSuccess, onFailure] = serviceSpy.mock.calls[0];
// execute the callbacks
onSuccess();
onFailure();
});

ES6 class jest mocking

I have an ES6 class which I need to mock it's methods. Following the documentation i made a manual mock of this, and got the constructor to both be called and asserted.
My function that consumes this class is just a basic function that runs one of the class methods.
test.js
const mockConnect = jest.fn();
const mockAccess = jest.fn();
jest.mock('../../src/connection');
const connection = require('../../src/connection').default;
connection.mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true),
};
});
caller_function();
expect(connection).toHaveBeenCalled(); // works properly as the constructor is called
expect(connection).toHaveBeenCalledWith('something'); // works
expect(mockAccess).toHaveBeenCalled(); // says it was not called when it should have
caller_function.js
import connection from 'connection';
const conn = new connection('something');
export function caller_function() {
conn.access(); // returns undefined when mock says it should return true
}
This is happening because you're using mockImplementation() instead of a manual mock or the factory parameter to jest.mock(), and your mocked object is being created during the module loading process, since the constructor call is not inside of any function. What's happening is:
The call to jest.mock('../../src/connection') runs and sets connection to be an automatic mock.
The conn object is created using the automatic mock. Therefore its access method returns undefined.
The call to mockImplementation() happens, changing the connection mock. However, since the conn object has already been created, it doesn't get the custom implementation.
Moving the constructor call into caller_function is one way to fix it:
export function caller_function() {
const conn = new connection('something');
conn.access();
}
You could also use the factory parameter to jest.mock(), specifying the implementation there, instead of calling mockImplementation(). That way you won't have to change your implementation code:
const mockConnect = jest.fn();
const mockAccess = jest.fn();
import connection from '../../src/connection';
jest.mock('./so-import', () => {
return jest.fn().mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true)
};
});
});
...
BTW the convention for ES6 class names is to begin with an uppercase letter. I was temporarily confused by the lowercase name connection.
Did you try doing connection.mockClear(); before you write a mockImplementation for the methods?
Also please refer to this https://jestjs.io/docs/en/es6-class-mocks

Resources