I'm writing unit tests for a Node.js module in Typescript, using Vitest. In one of the tests, the function that I'm testing is supposed to call another function (in a different module), so I've created a mocked version of that function.
Here is the setup code:
// unit tests for tested module
import _ from 'lodash';
import {
function_to_mock1,
function_to_mock2,
} from '../../../a/b/secondary_module.js';
import {
DOCUMENT
} from './documents.js';
let requestIdCounter = 0;
const TEST_OPTIONS: BaseOptions = {
logger: LOGGER,
logLevel: 'silent',
requestId: 'dummy-request-id',
};
vi.mock(
'../../../a/b/secondary_module.js',
async (importOriginal) => {
return {
...(await importOriginal<
typeof import('../../../a/b/secondary_module.js')
>()),
function_to_mock1: vi.fn(),
function_to_mock2: vi.fn(),
};
},
);
const function1Mock = vi.mocked(function_to_mock1);
const function2Mock = vi.mocked(function_to_mock2);
function1Mock.mockImplementation(
async () => {
console.log(`function1Mock called with request id ${requestId}`);
return fakeResult();
},
);
function setupTest(targetDocument: Document): void {
const mockDocument = _.cloneDeep(targetDocument);
function2Mock.mockImplementation(async () => mockDocument);
}
const TEST_ASSETS: Assets[] = [
// asset1
{...},
// asset2
{...},
// asset3
{...},
// asset4
{...},
];
This is the actual code being tested:
// tested module
export async function testedFunction(
{ sourceDocument, assets }: TestedFunctionInput,
options: BaseOptions,
): Promise<Document> {
...
let targetDocument = cloneDeep(sourceDocument);
const {...} = assets;
...
targetDocument = await internalFunction(
{
sourceDocument,
targetDocument,
...
},
options,
);
if (condition) {
someOtherFunction(input);
}
...
return targetDocument;
}
async function internalFunction(
{...}: InternalFunctionInput,
options: BaseOptions,
): Promise<Document> {
try {
...
const {
property: { innerProperty },
} = await function_to_mock1(...);
...
targetDocument = await function_to_mock2(...);
return targetDocument;
} catch (err) {
throw new UnprocessableEntityError('An error occurred');
}
}
Here is the test code. I commented out the contents of beforeEach
, beforeAll
, and afterAll
because they were causing some weird behaviors. For example, the vi.restoreAllMocks()
in afterAll
was being executed after each iteration of the parameterized test, instead of being executed after all the tests had finished running.
// unit tests for tested module - continued
describe.each(TEST_ASSETS)('myModule', async (assetObject: Assets) => {
beforeAll(() => {
// ...
});
beforeEach(() => {
// vi.clearAllMocks();
// ...
});
afterAll(() => {
// vi.restoreAllMocks();
});
it('should do something', async () => {
//Arrange
const inputDocument: Document = _.cloneDeep(DOCUMENT);
setupTest(inputDocument);
//Act
const actualResultDocument = await testedFunction(
{
source: inputDocument,
assets: assetObject,
},
{ ...TEST_OPTIONS, requestId: requestIdCounter.toString() },
);
requestIdCounter++;
//Assert
expect(actualResultDocument).toEqual(inputDocument);
expect(function1Mock).toHaveBeenCalledOnce();
expect(function2Mock).toHaveBeenCalledOnce();
console.log('made it past the mock assertions!');
... // more assertions
This is a parameterized test, and it runs on 4 different data sets. This is the test output when I run it on all 4 sets:
function1Mock called with request id 0
made it past the mock assertions!
function1Mock called with request id 1
function1Mock called with request id 2
function1Mock called with request id 3
Judging by some console.log
statements I've since removed, the second assertion (the one for function1Mock
) fails after the first test.
Edit: I think I know what the problem is. function1Mock
gets called once every time the test is run, so by the second iteration of the same test, the assertion expect(function1Mock).toHaveBeenCalledOnce()
fails.
So what I tried doing is clearing all mocks and setting up the mock implementation again in beforeEach
, thus:
console.log('running BeforeEach()');
vi.clearAllMocks();
function1Mock.mockImplementation(async () =>
returnFakeResult());
The problem is, it looks like beforeEach()
is not being executed at all. The log statement isn't being printed out to the console, and I'm baffled as to why.
I've tried reading the Vitest documentation, googling it and even asking ChatGPT, but I can't seem to find an explanation for why beforeEach
isn't being run. Can anyone help me out? Thanks!
It turns out the answer was very simple.
BeforeEach
was not being executed because it was accidentally imported from the node:test
package and not from the vitest
package.
Just goes to show that sometimes it's worth going over the obvious stuff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With