Create React App changes behaviour of jest.fn() when mocking async function - javascript

I am confused about the below behaviour of jest.fn() when run from a clean CRA project created using npx create-react-app jest-fn-behaviour.
Example:
describe("jest.fn behaviour", () => {
const getFunc = async () => {
return new Promise((res) => {
setTimeout(() => {
res("some-response");
}, 500)
});;
}
const getFuncOuterMock = jest.fn(getFunc);
test("works fine", async () => {
const getFuncInnerMock = jest.fn(getFunc);
const result = await getFuncInnerMock();
expect(result).toBe("some-response"); // passes
})
test("does not work", async () => {
const result = await getFuncOuterMock();
expect(result).toBe("some-response"); // fails - Received: undefined
})
});
The above test will work as expected in a clean JavaScript project but not in a CRA project.
Can someone please explain why the second test fails? It appears to me that when mocking an async function jest.fn() will not work as expected when called within a non-async function (e.g. describe above). It will work only when called within an async function (test above). But why would CRA alter the behaviour in such a way?

The reason for this is, as I mentioned in another answer, that CRA's default Jest setup includes the following line:
resetMocks: true,
Per the Jest docs, that means (emphasis mine):
Automatically reset mock state before every test. Equivalent to
calling jest.resetAllMocks() before each test. This will lead to
any mocks having their fake implementations removed but does not
restore their initial implementation.
As I pointed out in the comments, your mock is created at test discovery time, when Jest is locating all of the specs and calling the describe (but not it/test) callbacks, not at execution time, when it calls the spec callbacks. Therefore its mock implementation is pointless, as it's cleared before any test gets to run.
Instead, you have three options:
As you've seen, creating the mock inside the test itself works. Reconfiguring an existing mock inside the test would also work, e.g. getFuncOuterMock.mockImplementation(getFunc) (or just getFuncOuterMock.mockResolvedValue("some-response")).
You could move the mock creation and/or configuration into a beforeEach callback; these are executed after all the mocks get reset:
describe("jest.fn behaviour", () => {
let getFuncOuterMock;
// or `const getFuncOuterMock = jest.fn();`
beforeEach(() => {
getFuncOuterMock = jest.fn(getFunc);
// or `getFuncOuterMock.mockImplementation(getFunc);`
});
...
});
resetMocks is one of CRA's supported keys for overriding Jest configuration, so you could add:
"jest": {
"resetMocks": false
},
into your package.json.
However, note that this can lead to false positive tests where you expect(someMock).toHaveBeenCalledWith(some, args) and it passes due to an interaction with the mock in a different test. If you're going to disable the automatic resetting, you should also change the implementation to create the mock in beforeEach (i.e. the let getFuncOuterMock; example in option 2) to avoid state leaking between tests.
Note that this is nothing to do with sync vs. async, or anything other than mock lifecycle; you'd see the same behaviour with the following example in a CRA project (or a vanilla JS project with the resetMocks: true Jest configuration):
describe("the problem", () => {
const mock = jest.fn(() => "foo");
it("got reset before I was executed", () => {
expect(mock()).toEqual("foo");
});
});
● the problem › got reset before I was executed
expect(received).toEqual(expected) // deep equality
Expected: "foo"
Received: undefined

Related

Jest spyOn method without mockImplementation invocation is mocking implementation

So according to Jest documentation and multiple posts on the web and so was my belief, using jest.spyOn(foo, "bar") would wrap the specified function with methods that allow us to perform assertions over foo.bar without changing the actual implementation. To change the implementation we would need to use mockImplementation etc...
However, I am experiencing an issue where using jest.spyOn(foo, "bar") is clearly behaving like a jest.fn() mock.
/**
* Test causing the issue
*/
test("when invoked with custom options does not override include locations setting", async () => {
const defaultQueryConfig = { include: { locations: true } }
const findFirstSpy = jest.spyOn(prismaConnection.event, "findFirst")
await findOneEventByIdWithLocation(666, { include: { locations: false } })
expect(findFirstSpy).toHaveBeenCalledTimes(1)
expect(findFirstSpy).toHaveBeenCalledWith(expect.objectContaining(defaultQueryConfig))
})
/**
* Another test that runs in the suite at a later point in time fails
* because instead the object it is trying the result is `undefined`
*/
test("when event locations are requested it returns events with related locations", async () => {
const events = await findManyEventsWithLocations()
const locations = events.data.find((e) => e.id === 4)?.locations[0]
expect(locations).toEqual({
...eventLocation,
id: 1,
eventId: 4,
createdAt: expect.any(Date),
updatedAt: expect.any(Date)
})
})
However, if I add a statement: findFirstSpy.mockRestore() after the expectations of the first test the second test will pass fine and dandy... But this is only valid if the first test passes since the test will not get a chance to "mockRestore" if a previous assertion fails.
I could always add a "describe" with a "beforeEach"/"afterEach" around this first test, but I think this should not be the solution, considering jest.spyOn should not change implementation unless explicitly requested.
Does anybody know why this happens?
Result without findFirstSpy.mockRestore()
Result with findFirstSpy.mockRestore()

It seems that the beforeEach hook is executing for another test class (Mocha testing framework)

I am not sure what is happening.
I am using the Mocha testing framework to learn writing tests for solidity classes.
I met a strange behaviour, that I never met before. I have some previous experience with Mocha (with Cypress), but never saw this strange behaviour.
What is the problem:
I have two testing classes.
The first class:
//01. Call library modules.
const assert = require('assert'); // Declare an assertion library module.
const { beforeEach } = require('mocha');
//02. Create an example class with some logic inside.
class exampleClass {
func1() {
return 'random value 1';
}
func2() {
return 'random_value_2';
}
}
//03. Declare global variables.
let exam;
//04. Declare the "beforeEach" function. This function will be executed before every "it" test.
beforeEach(() => {
exam = new exampleClass(); // Declare a constructor for exampleClass.
});
//05. Create a test suite using "describe" function.
describe('name of the describe', () => {
//06. Add tests using "it" function.
it('This example shows how the assert will pass', () => {
assert.equal(exam.func1(), 'random value 1'); // Make an assertion to verify that the two values are equal.
});
xit('This example shows how the assert will fail, but the test will be skipped because the "X" symbol is added like a prefix for "it".', () => {
assert.equal(exam.func1(), 'random value'); // Make an assertion to verify that the two values are equal.
});
it('This example shows how the assert will pass', () => {
assert.equal(exam.func2(), 'random_value_2'); // Make an assertion to verify that the two values are equal.
});
});
The first class is a basic example of using the Mocha testing framework.
The second class:
//01. Call library modules.
const ganache = require('ganache'); // Declare a ganache library module.
const Web3 = require('web3'); // Declare a web3 library module.
//02. Create an instances.
const web3 = new Web3(ganache.provider()); // Declare an instance for Web3 including ganache.
//03. Declare the "beforeEach" function. This function will be executed before every "it" test.
beforeEach(() => {
// Get a list of all accounts.
web3.eth.getAccounts()
.then(fetchedAccounts => { // Get an access to all of the accounts. We use then, because "getAccounts()" function is returning a promise.
console.log(fetchedAccounts);
});
});
//04. Create a test suite using "describe" function.
describe('Generate fake eth accounts example 1', () => {
//05. Add tests using "it" function.
it('A0.1. example 1', () => {
});
});
The second class is an example of the generation of fake data (fake eth accounts).
If I execute the classes separately - everything is working as expected.
But if I execute all tests (both classes) - it seems that the "beforeEach" hook from class 2 is applying to class 1 for some reason.
As result, the console printed fake generated accounts three times. Three because 3 tests were passed. I am not sure why this is happening.

How to mock JSON file in Jest

I am using Jest to test my API and when I run my tests, my JSON file results.json gets written to due to the following line in my API app.js (which I don't want happening):
fs.writeFile('results.json', JSON.stringify(json), (err, result) => {
if (err) console.log('error', err);
});
This is what my Jest file looks like:
const request = require('supertest');
const app = require('./app');
// Nico Tejera at https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object
function serialise(obj){
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
}
describe('Test /addtask', () => {
test('POST /addtask Successfully redirects if newDate and newTask filled in correctly', () => {
const params = {
newTask: 'Example',
newDate: '2020-03-11'
};
return request(app)
.post('/addtask')
.send(serialise(params))
.expect(301);
});
});
I tried creating a mock of the JSON file and placed it outside the describe statement to prevent the actual results.json file being written to:
jest.mock('./results.json', () => ({ name: 'preset1', JSONtask: [], JSONcomplete: [] }, { name: 'preset2', JSONtask: [], JSONcomplete: [] }));
But this doesn't change anything. Does anyone have any suggestions?
I have seen other solutions to similar problems but they don't provide the answer I'm looking for.
EDIT: Although not a very good method, one solution to my problem is to wrap the fs.writeFile within the statement
if (process.env.NODE_ENV !== 'test') {
//code
};
although this would mean that fs.writeFile cannot be tested upon.
NOTE: I am still accepting answers!
Your issue is that the code you want to test has a hard-coded I/O operation in it, which always makes things harder to test.
What you'll want to do is to isolate the dependency on fs.writeFile, for example into something like a ResultsWriter. That dependency can then be injected and mocked for your test purposes.
I wrote an extensive example on a very similar case with NestJS yesterday under how to unit test a class extending an abstract class reading environment variables, which you can hopefully adapt to your needs.
jest.mock(path, factory) is for mocking JS modules, not file content.
You should instead mock fs.writeFile and check that it has been called with the expected arguments. The docs explain how to do it.

How to get/set out-of-scope variables when testing a function with dependency on these (jest)?

I'm trying to have full test coverage on some helper functions in my project. My first tests for either of the functions checks whether 2 out-of-scope variables are declared or not, which run successfully.
What I want to do is, jest to set/mock wrapper and layer variables declared out-of-scope of the functions to be tested, but can't seem to have it right no matter how hard I tried.
Thankful and appreciate any kind of help in this direction as I'm fairly rookie in terms of writing tests in general not to mention tests for jest.
I have tried jest.mock by mocking by default my imported module and then did jest.requireActual on 2 functions.
I have tried jest.doMock with a module factory which sets these 2 variables and then returns in an object.
Module: helpers/index.js
let wrapper; // Is created on runtime ergo initially undefined.
const layer = document.createElement('div');
layer.classList.add('layer');
const isElement = elm => {
if (!(elm instanceof Element)) {
throw new Error('Given element is not of type Element or is undefined!');
}
}
export const someFn = async () => {
wrapper = document.querySelector('.wrapper');
isElement(wrapper);
isElement(layer);
// Code that needs to be tested
// but cannot since when testing this function
// 'wrapper' and 'layer' are always undefined.
}
Test: helpers/index.test.js
// Helpers
import {someFn} from 'helpers';
describe('Method: someFn', () => {
test('should NOT throw', async () => {
await expect(someFn()).resolves.not.toThrow(); // but it does!
});
});
Actual result: Received promise rejected instead of resolved
Expected result: Not to throw, considering these out-of-scope variables that this function relies on, can somehow be set/mocked while testing it.

Mocking a Node Module which uses chained function calls with Jest in Node

Allow me to note that a similar question to this one can be found here, but the accepted answer's solution did not work for me. There was another question along the same lines, the answer of which suggested to directly manipulate the function's prototypes, but that was equally non-fruitful.
I am attempting to use Jest to mock this NPM Module, called "sharp". It takes an image buffer and performs image processing/manipulation operations upon it.
The actual implementation of the module in my codebase is as follows:
const sharp = require('sharp');
module.exports = class ImageProcessingAdapter {
async processImageWithDefaultConfiguration(buffer, size, options) {
return await sharp(buffer)
.resize(size)
.jpeg(options)
.toBuffer();
}
}
You can see that the module uses a chained function API, meaning the mock has to have each function return this.
The Unit Test itself can be found here:
jest.mock('sharp');
const sharp = require('sharp');
const ImageProcessingAdapter = require('./../../adapters/sharp/ImageProcessingAdapter');
test('Should call module functions with correct arguments', async () => {
// Mock values
const buffer = Buffer.from('a buffer');
const size = { width: 10, height: 10 };
const options = 'options';
// SUT
await new ImageProcessingAdapter().processImageWithDefaultConfiguration(buffer, size, options);
// Assertions
expect(sharp).toHaveBeenCalledWith(buffer);
expect(sharp().resize).toHaveBeenCalledWith(size);
expect(sharp().jpeg).toHaveBeenCalledWith(options);
});
Below are my attempts at mocking:
Attempt One
// __mocks__/sharp.js
module.exports = jest.genMockFromModule('sharp');
Result
Error: Maximum Call Stack Size Exceeded
Attempt Two
// __mocks__/sharp.js
module.exports = jest.fn().mockImplementation(() => ({
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer:jest.fn().mockReturnThis()
}));
Result
Expected mock function to have been called with:
[{"height": 10, "width": 10}]
But it was not called.
Question
I would appreciate any aid in figuring out how to properly mock this third-party module such that I can make assertions about the way in which the mock is called.
I have tried using sinon and proxyquire, and they don't seem to get the job done either.
Reproduction
An isolated reproduction of this issue can be found here.
Thanks.
Your second attempt is really close.
The only issue with it is that every time sharp gets called a new mocked object is returned with new resize, jpeg, and toBuffer mock functions...
...which means that when you test resize like this:
expect(sharp().resize).toHaveBeenCalledWith(size);
...you are actually testing a brand new resize mock function which hasn't been called.
To fix it, just make sure sharp always returns the same mocked object:
__mocks__/sharp.js
const result = {
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer: jest.fn().mockReturnThis()
}
module.exports = jest.fn(() => result);

Categories

Resources