mock functions don't work in .test files after adding setupFilesAfterEnv - node.js

After adding setupFilesAfterEnv in the jest.config.js like that:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ["./test/setupAfterEnv.ts"]
}
the mock functions don't work anymore in the .test.ts files but works in the file that was written inside the setupFilesAfterEnv like ./test/setupAfterEnv.ts
mock function example(the .test.ts file and setupAfterEnv.ts in the same director):
jest.mock("../src/service/sendEmail.ts", () => ({
send: () => {
return "MOCK sendEmail sent"
}
}));
I don't get any errors, and the other function that should be mocked runs. When I write wrong path in the mock function, I get the error can't find module. So it looks it find the function that should be mocked but doesn't run the mock function and run the other function instead
And if I commented the setupFilesAfterEnv config in jest.config.js it works again.

Documentation states
Warning: Importing a module in a setup file
(as specified by setupTestFrameworkScriptFile)
will prevent mocking for the module in question,
as well as all the modules that it imports.
If you have any imports/requires in your ./test/setupAfterEnv.ts, they likely imported other files, and anything chain imported in that process will not be able to be mock('../somemodule'); within tests.
I'm not sure why this restriction is in place; if you call the mock('../somemodule') within the setupAfterEnv.ts file itself, it will be mocked later within your tests. But I consider that bad, as only some tests may want to mock some modules, and others expect them not to be mocked. A pity. In my case, I wanted to import a typeorm library and connect to the database. But the class that creates this connection imports all our entities and all the entity classes import other classes which import other classes etc etc... so nothing becomes mockable. Simply unusable because of this restriction.

Related

Reuse Jest module mocks across tests

I want to mock a module for a test. Everything works, but I have to copy/paste the same code into every test file. How can I make this more maintainable?
(This is using Babel and TypeScript in a Next.js project.)
The production code looks like this:
import { useRouter } from 'next/router';
// Then call the hook in a React component:
const router = useRouter();
The test code:
// I need to access these mocks in the tests
const mockRouterPush = jest.fn();
const mockRouterBack = jest.fn();
jest.mock('next/router', () => ({
useRouter: () => ({
push: mockRouterPush,
query: { segment: 'seg1' },
back: mockRouterBack,
}),
}));
This works fine. The module and its hook are mocked for the test file, and I can refer to the mocks in the tests. The issue is that it's difficult to maintain across multiple test files.
Calls to jest.mock() are hoisted to the top of the file, and can only refer to variables that start with the word 'mock'.
With other mocks I've used require within the module factory instead of import; this works and reduces boilerplate, but (a) makes it more difficult for the tests to refer to the mocks and (b) my IDE (VSCode) won't auto-update require paths like it does with imports when I move files. For example:
jest.mock('react-i18next', () => {
// Sometimes the path is '../../../testhelpers' etc.
const { mockUseTranslation } = require('./testhelpers');
return {
useTranslation: mockUseTranslation,
};
});
I've tried doMock and createMockFromModule without success.
How do other folks deal with this issue?
Maybe using the __mocks__ directory can help you.
from the docs:
Manual mocks are defined by writing a module in a mocks/ subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/mocks directory.
You can also mock modules from the node_modules directory.
Manual Mocks

Jest Mock Globally From a Node Module

I am writing a series of Node modules that require a bunch of common Jest mocks to be setup before I can write unit tests.
I am trying to refactor the unit test setup into a separate module so I can avoid rewriting the setup each time.
The problem is that when I import the following code as a module it no longer mocks the other libraries, while it works just fine when the mocks are set up in a utility file.
Working Code:
jest.mock('#actions/core')
jest.mock('#actions/github')
const { GitHub, context} = require('#actions/github')
const core = require('#actions/core')
GitHub.mockImplementation(() => {
return {
{
repos: {
getContents: jest.fn()
}
}
}
}
module.exports = { core, GitHub, context }
I keep this in a utils.js file next to my test files and import it like const { core, GitHub, context } = require('./utils.js') and everything mocks as I expect. I can run expect().toHaveBeenCalledTimes() and get the numbers I expect.
The problem appears when I move utils.js to another module and require it.
I know that at bottom of the jest documentation it says "...Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module." But I am seeing this work inconsistently with the external file.
Anyone know how to get mocking setup in external modules?

Making assertions on Jest manual mocks

I'm having some trouble with manual Jest mocks and could use some help.
The file I am testing looks something like this:
import withTranslation from '../utils/withTranslation'
withTranslation('test')
I'm mocking a module in a test file like this.
import withTranslation from '../utils/withTranslation'
jest.mock('../utils/withTranslation')
// tests here
I have the manual mock at ../utils/__mocks__/withTranslation.js with the following code:
const impl = (...args) => {
console.log('in mock', args)
return args
}
export default impl
When the test runs, the mock is used and I can see the console log. So far so good.
However, I want to be able to make assertions on the usage of withTranslation when it is mocked. For example, expect(withTranslation).toHaveBeenCalledWith('test').
So, I change the manual mock to be a Jest mock function with the same implementation as before.
const impl = (...args) => {
console.log('in mock 1', args)
return args
}
// only difference is wrapping in jest.fn()
export default jest.fn(impl)
When the test runs now, a generic Jest mock function is used and I DO NOT see the console log, so my fake implementation is never called. Any ideas?
The code in my question is correct. The reason this was happening is because my test suite had a global beforeEach which was calling jest.resetMocks(). Hopefully this saves someone a few hours.
The function withTranslation in the actual module is already replaced with the mock before the test begins and this replacement is not done via reference. Hence when you assert the mock withTranslation the assertion fails because the mocked function is never called but rather the withTranslation method in the actual module is called.
Your console log still appears because as I said above the "real" module's function has already been replaced with the mock.
So import the actual withTranslation and assert it and it will resolve your problem.
Credit to this answer.

mocking es6 modules in unit test

Suppose we have a file(source.js) to test:
// source.js
import x from './x';
export default () => x();
And the unit test code is very simple:
// test.js
import test from 'ava';
import source from './source'
test("OK", t => {
source();
t.pass();
});
But this is the tricky thing. The file "./x" does not exist in the test environment. Since it is a unit test, we do not need "./x". We can mock the function "x()" anyhow(using sinon or something else). But the unit test tool, ava, keeps showing "Error: Cannot find module './x'";
Is there any way to run the unit test without the file "./x"?
To do this, you'd need to override the import process itself. There are libraries for doing something similar-- overriding the require function with proxyquire, for example-- but these force you to invoke their custom function to import the module under test. In other words, you'd need to abandon using ES6 module syntax to use them.
You should also note that ES modules are only supported experimentally (and relatively recently) in Node. If you're transpiling with Babel, you're not actually using ES modules. You're using the syntax, sure, but Babel transpiles them to CommonJS "equivalents", complete with require calls and module.exports assignments.
As such, if you're using Babel, you can probably proxyquire to import modules under test in your test files, even if you're using ES module syntax in those modules. This will break, though, if you ever switch away from Babel. :\
My personal recommendation would be to avoid directly exporting anything you might need to stub. So functions like x should be in a static module imported like import foo from './foo' and invoked like foo.x(). Then, you can easily stub it with sinon.stub(foo, 'x'). Of course, the './foo' file will have to exist for this still, but what it really comes down to is how hardcore you want to be about TDD practices versus how much complexity you're willing to introduce to your mocking/stubbing process. I prefer relaxing the former to avoid the latter, but in the end it's up to you.
If you are using Jest, it can be achieved easily by mocking the import using the inbuilt mock method of jest.
Please refer the link below to find more about ES6 import mocks
https://jestjs.io/docs/en/es6-class-mocks
If you are using jest with JavaScript this is very simple:
use jest.mock('MODULE_NAME')
If it is a node module or your own module, no problem jest will automatically mock that module.

How to export typings along with a module?

I'm writing a module that adds some functionalities to testing suites our team is using. It's a project that has main typescript file, that wraps other exports from our project.
index.d.ts:
export * from "./OneModule"
export * from "./AnotherModule"
One of the modules has a functionality of adding new methods to Chai.js assertion library. This is achieved by using it's functions for adding new methods, for example:
assertion.addChainableMethod('newMethod', callback, chainCallback)
will add ability to write statements like expect(something).to.be.newMethod(). TypeScript though, doesn't recognize those new methods, since the type Assertion in #types/chai obviously doesn't have those functions.
I've created interface definition to be merged with the Chai Assertion like this:
declare namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
The addChainableMethod adds new method so it can be also chaines as expect(something).to.be.newMethod.equals() that's why it has to be defined both as property and method.
The problem is, no matter how I add the above declaration to index.d.ts the new assertions aren't visible in the importing project. I suspect it's somehow wrapped in module namespace(but I might be very wrong about that). I know I can do #types/newAssertions and define them there, but that requires users to include 2 projects in package.json, and also requires our developers to handle 2 projects at once. How can I export both my Chai namespace extension and all the modules I have?
The best I've managed to do is something like that:
declare module 'my-module' {
function AddNewAssertions(chai: any, utils: any): void;
}
declare namespace Chai {
interface Assertion { //same as above }
}
But with this I cannot expose my other modules, and it works only when I'm only exposing AddNewAssertions function, which adds those assertions. This works perfectly though.
I knocked up a very basic test app for this, and I had some success with adding:
declare global {
namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
}
This makes the extension visible in your other modules, not just the one with the declaration.

Resources