Our React-Redux App uses create-react-app. We’re trying to back-apply Jest/Enzyme/redux-mock-store to a large code base. Our src/store.js file is the standard react-redux script that runs the reducers and constructs an initial state. This src/store.js is central to the problem.
Problem is that when we start the jest test-run, jest loads src/store.js 7 times, and then (roughly) once for every test. I must suppress this and can’t figure out how. The reason I must suppress it is that it always leaves the store in its initial state and there is one crucial AppConfig property in that store which must be populated to prevent javascript errors that stop jest. I already have a function which returns a mock store for use with the Provider. That works well. But our components call out from constructor(), componentDidMount(), etc to api functions. Those api functions don’t see the state dropped from Provider. Instead, the api functions do stuff like this:
// AppConfig is an immutable Map
const { AppConfig } = store.getState();
const stuff = AppConfig.get(‘stuff’).toJS();
That’s a JS error because AppConfig is empty and toJS() is called on undefined. (That can never be a problem in run-time because we construct no component without AppConfg being ready.) There are 2 solutions involving refactoring the App that I can do. But I’d rather figure out how to properly use jest. How can I supply the mock redux state without it getting clobbered by jest's repeated loads of src/store.js???
This is killing me. What in Jest, create-react-app, or ??? initiates these unwanted loads of src/store.js? How to override? Is there a config that causes jest to call my function instead of src/store.js?
I figured out my own problem. Will post here in case someone else that is new with jest may benefit from my folly.
Jest does not load src/store.js. The culprit is my shallow component tests because the components have statements like this:
import StuffService from '../../api/stuff';
The calls to the api from the component look like this:
// e.g. from componentDidMount
StuffService.get.myRequest();
But in the es6 module that defines StuffService, at the top we have something like:
import store from '../../store';
// lower in the js file, we have code as shown in my previous post:
const { AppConfig } = store.getState();
That's what is causing src/store to keep running. Will study Jest's Manual Mocks for a solution.
Related
Solved
Issue is tracked on github
I was attempting to test custom hooks using the react testing library, and the hook I was trying to put under test requires multiple context providers to work correctly. (authentication and notification)
The documentation here only outlines creating a wrapper with a single provider like so:
const wrapper = ({ children }) => <ContextProvider>{children}</ContextProvider>
However, my implementation needed something more complex, like so:
const wrapper = ({ children }) => (
<ToastProvider>
<NotificationProvider>
<AuthProvider>{children}</AuthProvider>
</NotificationProvider>
</ToastProvider>
);
This was was failing at every attempt with the errors:
TypeError: parentInstance.children.indexOf is not a function
OR
Invariant Violation: Drop(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
Leading me to believe there was no clear way to provide the right context without abandoning renderHook all together and building a test component that could fire off the necessary behaviors manually.
After a lot more digging I found this error buried in the logs:
Warning: An invalid container has been provided. This may indicate that another renderer is being used in addition to the test renderer. (For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) This is not supported.
Surely enough, it turns out there is a conflict with react-test-renderer and react-dom which causes calls to ReactDOM.createPortal to fail when under test. I assume this is somewhere in the ToastProvider, but the workaround is pretty simple.
Solved by adding this to the top of my test:
ReactDOM.createPortal = node => node
For development purposes I’d like to have the option to log all uses of useEffect inside my React app. Is there an easy way to extend the behaviour of this function? I’m using Webpack, in case this would provide us with an extra way of doing so.
Also, because this will lead to many console.logs, is there a way to tell them apart by providing information identifying the component that’s calling useEffect?
I would like to have this behaviour to “visually” check (in the console) if the application redundantly rerenders.
I would like to have this behaviour to “visually” check (in the console) if the application redundantly rerenders.
There is a library that does this for you: https://github.com/welldone-software/why-did-you-render#readme
The repo has a simple example to set it up:
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('#welldone-software/why-did-you-render');
whyDidYouRender(React);
}
TypeError: Cannot create property '__mobxInstanceCount' on boolean 'true'
at Object.<anonymous> (node_modules/mobx/lib/mobx.js:2620:38)
at Object.<anonymous> (src/Stores/DashboardStore.ts:9:14)
at Object.<anonymous> (src/__tests__/DashboardStore.test.ts:3:24)
Test Suites: 1 failed, 1 total
mobx.js:2620 mentions about global multiple instance.
Dashboard.test.ts
import DashboardStore from '../Stores/DashboardStore';
describe('DashboardStore', () => {
let dashboardStore;
beforeEach(() => {
dashboardStore = DashboardStore;
});
// tslint:disable-next-line:no-console
console.log(dashboardStore);
});
FAIL src\__tests__\DashboardStore.test.ts
? Test suite failed to run
My request is provide some hint on how to unit test a component and store.
Thanks you.
I don't know why, for me it was a problem in jest configuration file, when I tried to declare window as global.
It looks like your code imports mobx multiple times (looking at where this error comes from).
The messages seems to originate here out of the source: https://github.com/mobxjs/mobx/blob/master/src/core/globalstate.ts
The fail only triggers if __mobxInstanceCount is already set which means 2 different files globalstate.ts are included (maybe 2 different versions of mobx?)
aditional tips
Im not sure about jest, but with mocha the tests can run at the same time.
This means that the beforeEach does in this case not always ensure a clean dashboardStore variable. (beoreEach could run twice and then the 2 tests can run afterwards, giving issues, because there is only dashboardStore variable and is overwritten on every beforeEach() call).
Inject method
If you really want to make unit tests for your react components. i'd advise to abstract away from directly using the mobx store by using the #inject function instead: https://github.com/mobxjs/mobx-react#inject-as-function
This way you don't need to mock any mobx Store, but only need to insert the needed props in the react components. (keeping your components more declarative in what they really need.)
Let's say i have a React Component <forecast id="test"/>. And i want import this component into a legacy project which only have jquery involved.
Is it possible to get the value of this component like document.querySelector('#test').value?
I got some information from React website, that we cannot access data from outside the component. The recommended way is dispatching data from inside of the component.
My question is, the way to dispatching data is behind of the component implementation. Is it means that i have to read the source code of component in case i don't know how it works?
If this is true, i won't think React is free to inject to any product, it cost too much.
If you want to inject some React to your project you should do it with some independent part of your system.
If you have tightly coupled code base its always high cost to add any new technology to it. So its not a React problem. Try to find some independent module or subapplication in your system and move it to React. If you cannot find one, try to refactor existing code first.
You need to write a plain JS wrapper to do it. Something like this might work
function Forecast(element) {
this.value = initialValue;
React.render(<forecast onChange={onChange.bind(this)}/>, element);
function onChange(newValue) {
this.value = newValue;
}
}
I have a React view that communicates with a store. I've successfully tested views and stores separately, but not in combination.
I followed the structure documented here but received a TypeError. It looks like Jest is trying to register the store as a component even if I use dontMock.
Using Jest CLI v0.2.0
PASS __tests__/unit/spec/stores/ViewStore-spec.js (0.248s)
FAIL __tests__/unit/spec/components/View-spec.react.js (0.942s)
undefined
● View › it defaults to zero unread
- TypeError: /Users/matnorri/dev/projects/matnorri/web-client/src/app/scripts/components/View.react.js: /Users/matnorri/dev/projects/matnorri/web-client/src/app/scripts/stores/ViewStore.js: Cannot call method 'register' of undefined
at /Users/matnorri/dev/projects/matnorri/web-client/src/app/scripts/stores/ViewStore.js:43:31
at Object.runContentWithLocalBindings (/Users/matnorri/dev/projects/matnorri/web-client/node_modules/jest-cli/src/lib/utils.js:357:17)
...
I've included what I believe is the relevant code below, but can provide it in its entirety if necessary.
View Jest Test
jest.dontMock('../../../../src/app/scripts/components/View.react');
jest.dontMock('../../../../src/app/scripts/stores/ViewStore');
describe('View', function() {
var React;
var TestUtils;
var ViewComponent;
var View;
beforeEach(function() {
React = require('react/addons');
TestUtils = React.addons.TestUtils;
ViewComponent =
// Tried both lines with same effect.
// require('../../../../src/app/scripts/components/View.react.js');
require('../../../../src/app/scripts/components/View.react');
View = TestUtils.renderIntoDocument(<ViewComponent />);
});
it('defaults to zero unread', function() {
View._toString = jest.genMockFunction();
View._onChange();
expect(View.state.unread).toBe(0);
});
});
Thank you!
Answer to your question
I ran into this issue too and not mocking object-assign solved the issue for me. Add jest.dontMock('object-assign'); to your spec file. This will solve the problem.
// View Jest test
jest.dontMock('../../../../src/app/scripts/components/View.react');
jest.dontMock('../../../../src/app/scripts/stores/ViewStore');
To mock or not to mock
Some more information about when to "mock or not to mock" can be found on the Jest documentation website.
Jest modifies the default behavior of the "require" method and implements it's own require method. This way they are able to mock every function inside a file. So if you want something to really work (like with a store) you should not mock the file.
In your case you're not mocking the ViewStore so every function on this Store will be called. Because you didn't not mock object-assign the object assign function doesn't work. This way the ViewStore isn't created the right way (it returns undefined) and the test throws the error Cannot call method 'register' of undefined as your assign function didn't work, because it was mocked.
Again; If you read through the documentation on the Jest website, you'll find a detailed description about when to "mock or not to mock".
Extra info
I recommend to add "object-assign" and other commonly used modules (like lodash/underscore) to the unmockedModulePathPatterns in your test config. More information about this: https://facebook.github.io/jest/docs/api.html#config-unmockedmodulepathpatterns-array-string