Jest beforeAll vs beforeEach unexpected behaviour - javascript

I have a quite simple test, basically I'm trying to mock i18next's t function:
import { t } from 'i18next';
import { changeDocumentTitle } from './utils';
jest.mock('i18next');
const tMock = (key: string): string => key;
beforeAll(() => {
(t as jest.Mock).mockImplementation(tMock);
});
test('test changeDocumentTitle function', () => {
changeDocumentTitle('string');
expect(document.title).toEqual(tMock('string'));
});
and the changeDocumentTitle implementation:
import { t } from 'i18next';
export const changeDocumentTitle = (titleKey: string): void => {
document.title = t(`${titleKey}`);
};
Unfortunately, the test fails. But if I change it from beforeAll to beforeEach, everything's fine. How is that? Why beforeAll is not applying my mock unlike beforeEach?
Thanks in advance.

Since react-scripts v4 it added the default jest config resetMocks: true: https://github.com/facebook/create-react-app/releases/tag/v4.0.0
This means that the mocks are being reset before the tests runs with beforeAll. I recommended keeping resetMocks: true, because tests should be isolated.

Related

Jest spyon works when 'require' mocked module and doesn't work when 'import'

I want to test that my third-party-library (iframe-resizer) function is called.
My test
import React from 'react';
import { fireEvent, render } from 'react-testing-library';
//import * as depModule from 'iframe-resizer';
let depModule = require("iframe-resizer")
import { MyComponent } from '../my-component';
describe('Component', () => {
describe('Functional', () => {
it('should initialize iframeresizer when the iframe has loaded', async
() => {
const iframeResizerMock = jest.spyOn(depModule, "iframeResizer");
const { queryByTestId } = render(<MyComponent />);
fireEvent.load(queryByTestId('csb-iframe'));
expect(iframeResizerMock).toHaveBeenCalledTimes(1);
});
});
});
So the iframeResizer function is called,it was tested by console.log.The strange thing here is when I use
import * as depModule from 'iframe-resizer';
The spyon doesn't work(test fails)
But when I use
let depModule = require("iframe-resizer")
Everything works great(test passes) . I'm new in jest and node modules,so may be I missed simething obvious?
This happens because jest's mocking only works for commonjs modules, and in your case iframe-resizer does not ship commonjs modules, and jest does not transform node modules to commonjs.
Take a look at this thread. There are many interesting solutions in there.

Jasmine spy on RxJS 5.5 operators

I am trying to spy on RxJS operators with Jasmine. There are different use cases in my tests where I want to be in control on what a Observable returns. To illustrate what I am trying to do I have created the example above even thought it does not make to much sense as this observable always returns the same hard coded string. Anyway it is a good example to show what I am trying to achieve:
Imagine I have the following Class.
import {Observable} from 'rxjs/Observable';
import {of} from 'rxjs/observable/of';
export class AwesomeTest {
constructor() {
}
getHero(): Observable<string> {
return of('Spiderman');
}
}
And the following test:
import {AwesomeTest} from './awesomTest';
import {of} from 'rxjs/observable/of';
import createSpyObj = jasmine.createSpyObj;
import createSpy = jasmine.createSpy;
describe('Awesome Test', () => {
let sut;
beforeEach(() => {
sut = new AwesomeTest()
})
fit('must be true', () => {
// given
const expectedHero = 'Superman'
const asserter = {
next: hero => expect(hero).toBe(expectedHero),
error: () => fail()
}
createSpy(of).and.returnValue(of('Superman'))
// when
const hero$ = sut.getHero()
// then
hero$.subscribe(asserter)
});
});
I try to spy on the Observable of operator and return a Observable with a value that I specified inside my test instead of the actual value it will return. How can I achieve this?
Before the new Rx Import Syntax I was able to do something like this:
spyOn(Observable.prototype,'switchMap').and.returnValue(Observable.of(message))
In your spec file, everything as a wildcard (don't worry about tree shaking, this is just for the tests)
import * as rxjs from 'rxjs';
You can then use rxjs for your spying
spyOn(rxjs, 'switchMap').and.returnValue(rxjs.of(message))

Jest: mocking console.error - tests fails

The Problem:
I have a simple React component I'm using to learn to test components with Jest and Enzyme. As I'm working with props, I added the prop-types module to check for properties in development. prop-types uses console.error to alert when mandatory props are not passed or when props are the wrong data type.
I wanted to mock console.error to count the number of times it was called by prop-types as I passed in missing/mis-typed props.
Using this simplified example component and test, I'd expect the two tests to behave as such:
The first test with 0/2 required props should catch the mock calling twice.
The second test with 1/2 required props should catch the mock called once.
Instead, I get this:
The first test runs successfully.
The second test fails, complaining that the mock function was called zero times.
If I swap the order of the tests, the first works and the second fails.
If I split each test into an individual file, both work.
console.error output is suppressed, so it's clear it's mocked for both.
I'm sure I am missing something obvious, like clearing the mock wrong or whatever.
When I use the same structure against a module that exports a function, calling console.error some arbitrary number of times, things work.
It's when I test with enzyme/react that I hit this wall after the first test.
Sample App.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class App extends Component {
render(){
return(
<div>Hello world.</div>
);
}
};
App.propTypes = {
id : PropTypes.string.isRequired,
data : PropTypes.object.isRequired
};
Sample App.test.js
import React from 'react';
import { mount } from 'enzyme';
import App from './App';
console.error = jest.fn();
beforeEach(() => {
console.error.mockClear();
});
it('component logs two errors when no props are passed', () => {
const wrapper = mount(<App />);
expect(console.error).toHaveBeenCalledTimes(2);
});
it('component logs one error when only id is passed', () => {
const wrapper = mount(<App id="stringofstuff"/>);
expect(console.error).toHaveBeenCalledTimes(1);
});
Final note: Yeah, it's better to write the component to generate some user friendly output when props are missing, then test for that. But once I found this behavior, I wanted to figure out what I'm doing wrong as a way to improve my understanding. Clearly, I'm missing something.
I ran into a similar problem, just needed to cache the original method
const original = console.error
beforeEach(() => {
console.error = jest.fn()
console.error('you cant see me')
})
afterEach(() => {
console.error('you cant see me')
console.error = original
console.error('now you can')
})
Given the behavior explained by #DLyman, you could do it like that:
describe('desc', () => {
beforeAll(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterAll(() => {
console.error.mockRestore();
});
afterEach(() => {
console.error.mockClear();
});
it('x', () => {
// [...]
});
it('y', () => {
// [...]
});
it('throws [...]', () => {
shallow(<App />);
expect(console.error).toHaveBeenCalled();
expect(console.error.mock.calls[0][0]).toContain('The prop `id` is marked as required');
});
});
What guys wrote above is correct. I've encoutered similar problem and here's my solution. It takes also into consideration situation when you're doing some assertion on the mocked object:
beforeAll(() => {
// Create a spy on console (console.log in this case) and provide some mocked implementation
// In mocking global objects it's usually better than simple `jest.fn()`
// because you can `unmock` it in clean way doing `mockRestore`
jest.spyOn(console, 'log').mockImplementation(() => {});
});
afterAll(() => {
// Restore mock after all tests are done, so it won't affect other test suites
console.log.mockRestore();
});
afterEach(() => {
// Clear mock (all calls etc) after each test.
// It's needed when you're using console somewhere in the tests so you have clean mock each time
console.log.mockClear();
});
You didn't miss anything. There is a known issue (https://github.com/facebook/react/issues/7047) about missing error/warning messages.
If you switch your test cases ('...when only id is passed' - the fisrt, '...when no props are passed' - the second) and add such
console.log('mockedError', console.error.mock.calls); inside your test cases, you can see, that the message about missing id isn't triggered in the second test.
For my solutions I'm just wrapping original console and combine all messages into arrays. May be someone it will be needed.
const mockedMethods = ['log', 'warn', 'error']
export const { originalConsoleFuncs, consoleMessages } = mockedMethods.reduce(
(acc: any, method: any) => {
acc.originalConsoleFuncs[method] = console[method].bind(console)
acc.consoleMessages[method] = []
return acc
},
{
consoleMessages: {},
originalConsoleFuncs: {}
}
)
export const clearConsole = () =>
mockedMethods.forEach(method => {
consoleMessages[method] = []
})
export const mockConsole = (callOriginals?: boolean) => {
const createMockConsoleFunc = (method: any) => {
console[method] = (...args: any[]) => {
consoleMessages[method].push(args)
if (callOriginals) return originalConsoleFuncs[method](...args)
}
}
const deleteMockConsoleFunc = (method: any) => {
console[method] = originalConsoleFuncs[method]
consoleMessages[method] = []
}
beforeEach(() => {
mockedMethods.forEach((method: any) => {
createMockConsoleFunc(method)
})
})
afterEach(() => {
mockedMethods.forEach((method: any) => {
deleteMockConsoleFunc(method)
})
})
}

Mounting a vue instance with avoriaz, but cannot sinon spy on imported function

I have the following component script (some irrelevant bits removed):
import api from '#/lib/api';
export default {
methods: {
upload (formData) {
api.uploadFile(formData).then(response => {
this.$emit('input', response.data);
});
}
}
};
And I have the following test, which uses avoriaz to mount the Vue instance:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadForm from '#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.api = api;
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
My sinon spy is never called, and I've tried a couple different variations on the above. What is the best way to spy on an imported function like this? Or am I conceptually approaching this the wrong way?
Problem
You need to stub the api dependency, which is a dependency of the file. This can't be done through the vue instance, since api is not a part of the vue component.
You need to stub the file dependency.
Solution
One method to do this is to use inject-loader.
Steps
Install inject-loader
npm install --save-dev inject-loader
At the top of your file, import UploadForm with inject-loader and vue-loader:
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
This is a factory function that returns UploadForm with dependencies stubbed.
Now, in your test you need to call UploadFormFactory with the dependency you want stubbed:
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
So your test file will look like:
import { mount } from 'avoriaz';
import { expect } from 'chai';
import sinon from 'sinon';
import UploadFormFactory from '!!vue-loader?inject!#/components/UploadForm';
describe('upload', () => {
it('passes form data to api.uploadFile', () => {
const api = {
uploadFile: sinon.spy()
};
const UploadForm = UploadFormFactory({
'#/lib/api': api
})
const testFormData = { test: 'test' };
const api = {
uploadFile: sinon.spy()
};
const wrapper = mount(UploadForm);
wrapper.vm.upload(testFormData);
expect(api.uploadFile.called).to.equal(true);
});
});
More info
I've written a tutorial with more detail here - https://www.coding123.org/stub-dependencies-vue-unit-tests/
I think Edd's answer is the most encompassing for most scenarios, so I'm marking his as the accepted answer. However, the workaround I came up with was to make the api library a global service (Vue.prototype.$api = api) in my main.js file, and then overwrite the global with a stub before each test.
describe('UploadForm.vue', () => {
let wrapper;
const uploadFile = sinon.stub().returns(Promise.resolve({ data: 0 }));
beforeEach(() => {
wrapper = mount(UploadForm, {
globals: {
$api: { uploadFile }
}
});
});
// ...

How can I mock an ES6 module import using Jest?

I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --
The application code:
// myModule.js
import dependency from './dependency';
export default (x) => {
dependency.doSomething(x * 2);
}
And the test code:
//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
spyOn(dependency, 'doSomething');
myModule(2);
expect(dependency.doSomething).toHaveBeenCalledWith(4);
});
});
What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.
The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.
// myModule.js
export default (x) => {
const dependency = require('./dependency'); // Yuck
dependency.doSomething(x * 2);
}
//myModule-test.js
describe('myModule', () => {
it('calls the dependency with double the input', () => {
jest.mock('../dependency');
myModule(2);
const dependency = require('../dependency'); // Also yuck
expect(dependency.doSomething).toBeCalledWith(4);
});
});
For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.
Edit: Several years have passed and this isn't really the right way to do this any more (and probably never was, my bad).
Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.
I'm leaving this answer in its original form for historical purposes, but you should really use jest.spyOn or jest.mock. Refer to the jest docs or the other answers on this page for details.
Original answer follows:
I've been able to solve this by using a hack involving import *. It even works for both named and default exports!
For a named export:
// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';
export default (x) => {
doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.doSomething = jest.fn(); // Mutate the named export
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Or for a default export:
// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies
export default (x) => {
dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';
describe('myModule', () => {
it('calls the dependency with double the input', () => {
dependency.default = jest.fn(); // Mutate the default export
myModule(2);
expect(dependency.default).toBeCalledWith(4); // Assert against the default
});
});
You have to mock the module and set the spy by yourself:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}))
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export
Using only ES6 module syntax:
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.
To mock an ES6 dependency module default export using Jest:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);
describe('myModule', () => {
it('calls the dependency once with double the input', () => {
myModule(2);
expect(dependency).toHaveBeenCalledTimes(1);
expect(dependency).toHaveBeenCalledWith(4);
});
});
The other options didn't work for my case.
Adding more to Andreas' answer. I had the same problem with ES6 code, but I did not want to mutate the imports. That looked hacky. So I did this:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
And added file dependency.js in the " __ mocks __" folder parallel to file dependency.js. This worked for me. Also, this gave me the option to return suitable data from the mock implementation. Make sure you give the correct path to the module you want to mock.
The question is already answered, but you can resolve it like this:
File dependency.js
const doSomething = (x) => x
export default doSomething;
File myModule.js
import doSomething from "./dependency";
export default (x) => doSomething(x * 2);
File myModule.spec.js
jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";
describe('myModule', () => {
it('calls the dependency with double the input', () => {
doSomething.mockImplementation((x) => x * 10)
myModule(2);
expect(doSomething).toHaveBeenCalledWith(4);
console.log(myModule(2)) // 40
});
});
None of the answers here seemed to work for me (the original function was always being imported rather than the mock), and it seems that ESM support in Jest is still work in progress.
After discovering this comment, I found out that jest.mock() does not actually work with regular imports, because the imports are always run before the mock (this is now also officially documented). Because of this, I am importing my dependencies using await import(). This even works with a top-level await, so I just have to adapt my imports:
import { describe, expect, it, jest } from '#jest/globals';
jest.unstable_mockModule('../dependency', () => ({
doSomething: jest.fn()
}));
const myModule = await import('../myModule');
const dependency = await import('../dependency');
describe('myModule', async () => {
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
I solved this another way. Let's say you have your dependency.js
export const myFunction = () => { }
I create a depdency.mock.js file besides it with the following content:
export const mockFunction = jest.fn();
jest.mock('dependency.js', () => ({ myFunction: mockFunction }));
And in the test, before I import the file that has the dependency, I use:
import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'
it('my test', () => {
mockFunction.returnValue(false);
functionThatCallsDep();
expect(mockFunction).toHaveBeenCalled();
})
I tried all the solutions and none worked or were showing lots of TS errors.
This is how I solved it:
format.ts file:
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
class Format {
parseXml (xml: string) {
return camelcaseKeys(parse(xml), {
deep: true,
})
}
}
const format = new Format()
export { format }
format.test.ts file:
import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))
describe('parseXml', () => {
test('functions called', () => {
const result = format.parseXml('XML')
expect(parse).toHaveBeenCalledWith('XML')
expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
expect(result).toBe('camel cased')
})
})
I made some modifications on #cam-jackson original answer and side effects has gone. I used lodash library to deep clone the object under test and then made any modification I want on that object. But be ware that cloning heavy objects can have negative impact on test performance and test speed.
objectUndertest.js
const objectUnderTest = {};
export default objectUnderTest;
objectUnderTest.myFunctionUnterTest = () => {
return "this is original function";
};
objectUndertest.test.js
import _ from "lodash";
import objectUndertest from "./objectUndertest.js";
describe("objectUndertest", () => {
let mockObject = objectUndertest;
beforeEach(() => {
mockObject = _.cloneDeep(objectUndertest);
});
test("test function", () => {
mockObject.myFunctionUnterTest = () => {
return "this is mocked function.";
};
expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
});
});

Categories