Mock react-dom Jest - javascript

I'm trying to mock the react-dom module using Jest
import React from 'react';
import {configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Link from '../components/DumbComp';
import { shallowToJson } from 'enzyme-to-json';
import { render } from 'react-dom';
configure({ adapter: new Adapter() });
jest.mock('react-dom');
describe('Link',() =>{
it('should render correctly', ()=>{
expect(render).toHaveBeenCalledWith(
<Link title="mockTitle" url="mockUrl" />, 'element-node'
);
expect(render).toHaveBeenCalledTimes(1);
});
});
When I run the test I get the following error:
Link › should render correctly
expect(jest.fn()).toHaveBeenCalledWith(expected)
Expected mock function to have been called with:
[<Link title="mockTitle" url="mockUrl" />, "element-node"]
But it was not called.
It seems that when I mock the render method it doesn't return anything. How can I correctly mock it?
I'm using this tutorial, see under the "The art of mocking" section.

If you want to create a manual mock create in the same file like this:
jest.mock('react-dom', () => ({
render: jest.fn(),
}));
I would recommend to have a look at snapshot and use it togheter with enyzme. It makes testing easier because you can write something like this:
describe ('Link',() =>{
it ('should render correctly', ()=> {
const component = mount(
<Link title="mockTitle" url="mockUrl" />
);
expect(component).toMatchSnapshot();
});
});
Which gives you a snapshot of how the component got rendered exactly. You can also use it with function you test and it will give you a snapshot of all the calls your function got called with and the arguments the function got called with.

You should create a mock file next to your node_modules in __mocks__/react-dom.js
// react-dom.js
export default {
render: jest.fn(),
};

The accepted answer might be a little out of date. To mock react-dom without side-effects you need to only mock the function you need, which in this case is the render function.
This can be done using Jest's requireActual like so:
jest.mock('react-dom', () => ({
...jest.requireActual('react-dom'),
render: jest.fn(),
}))

Related

Can't call custom hook from custom hook in react

I have a React app created with create-react-app.
I'm trying to make a custom hook using Microsoft Authentication Library (MSAL). MSAL has a custom React hook that I want to call from my own custom hook.
When I use a hook (any hook) inside my custom hook in a separate file I get this in the browser:
Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
// ourhook/index.ts
import { useEffect } from "react";
export const useMsal2 = () => {
useEffect(() => {
console.log("Hello from our hook!");
});
};
// app.tsx
import React from "react";
import { useMsal2 } from "./ourhook";
const App = () => {
useMsal2();
return <div>App</div>;
};
export default App;
If I call
const { instance } = useMsal();
directly from App.tsx everything works fine. It only appears to be a problem if my custom hook is in its own file.
From what I see I'm not violating any hook rules. I'm calling a hook that's calling a hook, and the first call is from a top level component.
I have read other threads here about hooks in hooks, but none of them has an answer that fits this problem.
Have I missed something about hook rules, or what might be causing this?
Okay, I forgot that we tried to have /ourhook as a freestanding project and then copy pasted it into a create react app app.
Some of you were right, it did have its own version of react.
I'm just going to hide under a rock for the rest of the week.
Thanks for all your help! <3
Try to add this comment just above:
import { useMsal } from "#azure/msal-react";
export const useMsal2 = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { instance } = useMsal();
const request = "";
return {
loginRedirect: () => console.log(""),
}
};
I don't know what useMsal looks like, but from what I see, you don't actually violate any hook rule.

Mocking a module imported in a vue component with Jest

I'm having some issues processing the documentation of Jest, because I expected this code to work:
import Vue from 'vue';
import Router from '#/router/index';
import OrdersService from '#/services/orders.service';
jest.mock('#/services/orders.service');
describe('OrdersItem.vue', () => {
beforeEach(() => {
// mockClear does not exist
OrdersService.mockClear();
});
it('should render expected list contents', () => {
// Orders.mock is undefined
OrdersService.getAll.mockResolvedValue([ ... ]);
// ...
However it does not. It fails as-if OrdersService was never mocked. I've also tried stuff like:
jest.mock('#/services/orders.service', () => jest.fn());
jest.mock('#/services/orders.service', () => { getAll: jest.fn() });
First one replaces the whole service with a mock function (I'd like to achieve that automatic mocking functionality mentioned in the docs, where all the methods from the original are auto-replaced with mock fn).
Second one fails same way as the .mock call with just the module path.
What am I doing wrong here and why?
orders.service skeleton:
import axios from 'axios';
import config from '../config/config.json';
import Order from '../models/order';
class OrdersService {
constructor(httpClient) {
this.httpClient = httpClient;
}
getAll() {
// ...
}
}
export default new OrdersService(axios);
It looks like there is an issue in with jest.mock (#4262) concerning moduleNameMapper for module resolvers, aliases, path, whatever you want to call using #/something.
// you cannot use a module resolver (i.e. '#')
jest.mock('#/services/orders.service');
// you must use the full path to the file for the import and mock
import OrdersService from '../../src/services/orders.service';
jest.mock('../../src/services/orders.service');
Stay tuned to updates on the issue, looks like the last update was on 9/28.
Secondly, provided you fix the issue above, you are exporting a class instance not the class itself, as is done in the Jest example. Therefore, you will not have access to the clearMock method on the OrdersService but rather you can call clearMock on each mocked method on the class instance.
// mockClear will be undefined
OrdersService.mockClear();
// mockClear is defined
OrdersService.getAll.mockClear();
If you want to export the instance as you are, you could just clear all mocks using jest.clearAllMocks in the beforeEach or loop through all methods and call mockClear on each. Otherwise exporting the class itself will give you access to OrdersService.mockClear which will ...
Clear all instances and calls to constructor and all methods (ref)
This seems to be useful in cases where the mocked class is being used/instantiated in another class that you are trying to test, as in the jest example.
All of this has been tested and confirmed using Jest v23.6 and vue-cli v3.0.4.
Since the OrdersService is an instance of the class, it will return an object and you would need to mock all the properties exposed by this object manually.
You could try with the following implementation to mock your function. Reference docs
OrdersService.getAll = jest.fn(()=>{
// mock implementation here;
});
Hope this helps :)
You could try calling jest.resetModules() in the beforeEach block, that might cause the mocked service to be used
Try to import everything with an alias and set the mock on the alias.
import * as OrdersModule from '#/services/orders.service';
OrdersModule.getAll = jest.fn()
I found it in the bible:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Jest: How to mock default export Component when same module also has named export?

I have an ES6 module that exports a React Component class by default, but also exports a plain JS function as a named export. When testing other packages that use this module, I want to mock both the default exported component and named exported function to keep my unit tests pure.
The module looks something like this:
import React, { Component } from 'react';
export default class MyComponent extends Component {
render() {
return <div>Hello</div>
}
}
export function myUtilityFunction() { return 'foo' };
I would like to use the following syntax to mock the exports:
import React from 'react';
import MyComponent, { myUtilityFunction } from './module';
jest.mock('./module');
MyComponent.mockImplementation(() => 'MockComponent');
myUtilityFunction.mockImplementation(() => 'foo');
When I try to use this syntax, however, MyComponent does not appear to be mocked when used within other components. When I try to mock MyComponent like this and render it on its own, it renders out to null.
This behavior is very strange, because if I use the exact same syntax, but both imports are JavaScript functions, the mocking works as expected. See the StackOverflow issue I opened here that confirms that the syntax works when the imports are both functions.
Here is a GitHub repo demoing the problem, as well as several solutions I've tried: https://github.com/zpalexander/jest-enzyme-problem
You can build the repo and run the tests with yarn install && yarn test
Thanks!
The other solution didn't work for me. This is how I did:
jest.mock('./module', () => ({
__esModule: true,
myUtilityFunction: 'myUtilityFunction',
default: 'MyComponent'
}));
Another way to do it:
jest.unmock('../src/dependency');
const myModule = require('../src/dependency');
myModule.utilityFunction = 'your mock'
I think the issue is that the ShallowWrapper class's getElement method needs to be passed a class that contains a render method. To do that, your MyComponent.mockImplementation needs to more fully mock a class constructor.
For details on how to mock a class constructor, see the Jest documentation starting at "mockImplementation can also be used to mock class constructors:" https://facebook.github.io/jest/docs/en/mock-function-api.html#mockfnmockimplementationfn
Using the Jest documentation as a model, we can mock the MyComponent class constructor and make it shallow renderable by enzyme like this:
MyComponent.mockImplementation(() => {
return {
render: () => <div>MockComponent</div>
};
});
Now when getElement goes looking for a render method it will find it.
Here's a gist that implements this change on the App.mockImplementation.test.js file from your repo: https://gist.github.com/timothyjellison/a9c9c2fdfb0b30aab5698dd92e901b24

Jest mocking default exports - require vs import

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

jquery doesn't work with jsdom/enzyme

I have a minimum test react app with following component:
import React from 'react';
import $ from 'jquery';
export default class App extends React.Component {
componentDidMount() {
console.log('componentDidMount', $('#helloDiv').length);
}
render() {
return <div id='helloDiv'>
Hello React!
</div>;
}
}
this works fine when loading it in browser (Chrome). The console.log() in componentDidMount() prints out 1 helloDiv element found
However, if I run the test using mocha + enzyme + jsdom, the same console.log() in App component prints out 0:
import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import App from '../src/client/app/first'
describe('first test', () => {
it('should pass', () => {
const app = mount(<App />);
expect(app.find('#helloDiv').length).to.eq(1);
});
});
Note: I don't have problem with this unit test, it's passing. The real problem is when < App /> is mounted using enzyme, componentDidMount() is called but the console.log() statement inside it prints out 0, instead of 1
Here is how I run mocha:
mocha --require enzyme/withDom --compilers js:babel-core/register test/index.test.js
Any idea why jquery selector doesn't find anything in the test? It should not be mocha issue because the same issue happens if I change to jest
Finally found the issue:
Enzyme mount(<SomeComponent />) by default will do full DOM rendering but not insert the rendered component into current document (JSDom). That's why jQuery cannot find any element in current document
To do full DOM rendering AND attach to current document:
mount(<SomeComponent />, { attachTo: document.getElementById('app') });
Where app is empty div available when jsdom is setup:
global.document = jsdom('<html><head></head><body><div id="app" /></body></html>');
There needs to be some setup done before you could jsdom with jquery in node-env.
Try this if it helps.
Create a test helper file like this -
test_helper.js
import _$ from 'jquery';
import jsdom from 'jsdom';
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = global.window.navigator;
const $ = _$(window);
chaiJquery(chai, chai.util, $);
export {expect};
While running -
mocha --require enzyme/withDom --compilers js:babel-core/register --require test/test_helper.js test/index.test.js
or another way use jsdom-global without test_helper.js file.
npm install --save-dev jsdom-global
Then :
import 'jsdom-global/register';
//at the top of file , even , before importing react
I couldn't get Phuong Nguyen's answer to work. I did find the relevant page in the enzyme docs. I ended up with something like, based on the final example on that page:
const div = global.document.createElement('div');
global.document.body.appendChild(graphDiv);
const wrapper = mount(<SomeComponent />, { attachTo: div }); // same as the other answer
// clean up after ourselves
wrapper.detach();
global.document.body.removeChild(div);

Categories

Resources