Mock basename in Jest / Enzyme - reactjs

I have a history class that looks like:
import createHistory from 'history/createBrowserHistory';
export default createHistory({
basename: '/admin/',
});
When writing / running any unit tests against a class that is connected to the store and rendered using a router, I am getting the following warning in tests:
console.error node_modules/warning/warning.js:51
Warning: You are attempting to use a basename on a page whose URL path does not begin with the basename. Expected path "blank" to begin with "/admin".
An example test would be as follows:
import React from 'react';
import { MemoryRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import TenantListContainer from '../../../src/containers/TenantList';
import TenantList from '../../../src/containers/TenantList/TenantList';
const mockStore = configureStore();
const store = mockStore({
tenants: {
tenants: ['foo'],
loading: true,
},
});
describe('TenantListContainer', () => {
it('should render the TenantList components', () => {
const wrapper = mount(
<Router>
<TenantListContainer store={store} />
</Router>
);
expect(wrapper.find(<TenantList />)).toBeTruthy();
});
});
How can I mock out this history prop, using the MemoryRouter?
I have tried passing in the history object, however I am then told this prop is ignored by the memory router.

You can always mock out the url from within your Jest config.
My approach generally is including this within my package.json
In your case, I would expect this to be something like -
"jest": {
"testURL": "http://some-domain.tld/admin"
}
You can then change this on a per test basis by including the following in your beforeEach() block
window.history.pushState({}, 'Foo Title', '/admin/foo');

For those using create-react-app: update package.json and add the url at the end of the test command line like this:
react-scripts-ts test --env=jsdom --watch --testURL http://localhost/foo
Source: https://github.com/facebook/create-react-app/issues/3425

You need to set proper testURL for jest. This is done in jest.config.js, for example like this:
module.exports = {
...
testURL: 'http://localhost/admin',
}

Related

React router and createMemoryHistory in test: Property 'location' does not exist on type 'History' when history is defined in beforeEach

I am testing a navbar component that uses React Router. I am trying to use createMemoryHistory from the history package to test browser navigation.
I am using react-router-dom v 6.3.0 and history v 5.3.0
My test looks like this:
import { render, screen } from "#testing-library/react";
import React from "react";
import { Router } from "react-router-dom";
import { createMemoryHistory } from "history";
import { NavBarComponent } from "../navBarComponent";
import userEvent from "#testing-library/user-event";
describe("<NavBarComponent/>", () => {
beforeEach(() => {
const history = createMemoryHistory({ initialEntries: ["/"] });
render(
<Router location={history.location} navigator={history}>
<NavBarComponent></NavBarComponent>
</Router>
);
});
describe("Clicking on buttons to change route", () => {
it("navigates to correct paths", () => {
expect(history.location.pathname).toBe("/"); // <----- IDE error is here: Property 'location' does not exist on type 'History'
userEvent.click(screen.getByText("Collection"));
expect(history.location.pathname).toBe("/collection");
});
});
});
Running the test gives the following error:
TypeError: Cannot read properties of undefined (reading 'pathname')
36 | // </Router>
37 | // );
> 38 | expect(history.location.pathname).toBe("/");
However, when I break out the beforeEach code into each individual test, the test works fine:
import { render, screen } from "#testing-library/react";
import React from "react";
import { Router } from "react-router-dom";
import { createMemoryHistory } from "history";
import { NavBarComponent } from "../navBarComponent";
import userEvent from "#testing-library/user-event";
describe("<NavBarComponent/>", () => {
describe("Clicking on buttons to change route", () => {
it("navigates to correct paths", () => {
const history = createMemoryHistory({ initialEntries: ["/"] });
render(
<Router location={history.location} navigator={history}>
<NavBarComponent></NavBarComponent>
</Router>
);
expect(history.location.pathname).toBe("/");
userEvent.click(screen.getByText("Collection"));
expect(history.location.pathname).toBe("/collection");
});
});
});
My IDE indicates that the type of history when it is defined in the test itself is MemoryHistory, and that is also the type when it is defined in beforeEach.
However, accessing the history object inside of a test when it was defined in beforeEach indicates that the type is History, not MemoryHistory.
What's going on here? Why does the type seem to change when the object is defined in beforeEach as opposed to inside the test itself?
Your IDE shows the correct hint. You declare a constant 'history' with what 'createMemoryHistory' returns in another lexical scope (beforeEach hook). Thus, the 'history' variable inside of the test is something absolutely unrelated. In fact, it is a global object 'history' that is available through web APIs (docs). Would you call your variable in the beforeEach hook anything else, like 'myHistory', it would be simply 'undefined' inside of the test.
What you should do is move the declaration of 'history' to the outer scope, where it will be available for all tests. For example, under the first 'describe'. If you need to recreate it for each test, you can declare it as a 'let' and reassign it in beforeEach hook.

Adding the properties to the window object and using it before the import of components

I have a Home component, so I am writing a test case for the component the problem is inside the Home Component I am requiring a config.js file and multiple components which inside they use config.js
The file is basically the configuration for the application.
So the values comes from window._config_ object, so to add the properties in my test case. I have tried the below approach.
import React from "react";
import { mount, shallow } from "enzyme";
import Home from "..";
describe("Home Page", () => {
beforeEach(() => {
window._config_ = {
URL: "http://www.sample.com",
};
});
it("should render Home Page", () => {
console.log("here", window._env_);
const wrapper = shallow(<Home />);
expect(wrapper).toMatchSnapshot();
});
});
So since the Home component is imported at the top, it will import the
related config files since inside the describe block only I am adding the
properties
In the above case I am getting the error as TypeError: Cannot read property 'URL' of undefined
If I comment Home component its working, so I have tried importing Home inside the 'it' block but for import should be done at the top level, so then I tried using common js require way but that's giving me the below error:
it("should render Home Page", () => {
console.log("here", window._env_);
const Home = require("..");
const wrapper = shallow(<Home />);
}
ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `object`.
I am using CRA for React app.
How should I solve this?
Update
Tried this also but how should I add it, when I added window inside global it says
Out of the box, Create React App only supports overriding these Jest options:
• collectCoverageFrom
• coverageReporters
• coverageThreshold
• coveragePathIgnorePatterns
• extraGlobals
• globalSetup
• globalTeardown
• moduleNameMapper
• resetMocks
• resetModules
• snapshotSerializers
• transform
• transformIgnorePatterns
• watchPathIgnorePatterns.
These options in your package.json Jest configuration are not currently supported by Create React App:
Finally found out, so you can add the global variables in setupTests.js
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
global.config = {
URL: "http://www.example.com",
};
configure({ adapter: new Adapter() });
And in every test file you can access like this
it("should render Home Page", () => {
console.log("here", window.config.URL);
const wrapper = shallow(<Home />);
expect(wrapper).toMatchSnapshot();
});

Jest cannot find module from file in the same folder

I'm trying to set up a very basic test with Jest which tests whether App.js renders correctly. I am getting the error
Cannot find module './App' from 'App.test.js'
However, Jest was able to find:
'./App.js'
'./App.test.js'
However, if I try to write import App from "./App.js"; instead of ... from "./App";, I get
Cannot find module './App.js' from 'App.test.js'
How can I make Jest find modules properly?
The project was set up using Create React App, and App.js and App.test.js are located within the same folder (src/components).
App.js
import React, { Component } from "react";
class App extends Component {
render() {
return <div />;
}
}
export default App;
App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./App.js";
const app = shallow(<App />);
it("renders correctly", () => {
expect(app).toMatchSnapshot();
});
I think Jest needs to set up itself before rendering components (so don't call <App /> outside of test cases):
it("renders correctly", () => {
const app = shallow(<App />);
expect(app).toMatchSnapshot();
});
However, if the problem is on the import line, assuming you use an up-to-date version and don't pass any CLI options yourself, I would recommend to replace whole content of App.test.js with:
it('', () => console.log(process.env))
and search for the listed environment variables in https://jestjs.io/docs/en/configuration to see if any can affect Jest.

Unit Testing dynamically imported React Component

I have a very simple React component that uses react-loadable to dynamically import another component. The code looks something akin to the following:
import React from 'react';
import Loadable from 'react-loadable';
import LoaderComponent from 'path/to/LoaderComponent';
export default outerLoadableComponent = Loadable({
loader: () => import('path/to/innerComponent'),
loading() {
return <LoaderComponent />
}
});
I am attempting to test this component by using Enzyme to mount outerLoadableComponent, which creates a wrapper around outerLoadableComponent where I can see that the LoaderComponent wrapping it has the loadingState set to true. However, I am stuck at the point where the inner import does not resolve. It seems to be a promise that would only resolve should the import actually go through, however even with some timeouts, it does not work:
const expect = chai.expect;
chai.use(sinonChai);
it('should render the loading state, and innerComponent', (done) => {
const wrapper = mount(
<outerLoadableComponent />
);
expect(wrapper.loading).to.be.true;
setTimeout(() => {
expect(wrapper.loading).to.be.false;
expect(wrapper.loaded).to.exist; // loaded state returns a function
expect(wrapper.find(innerComponent)).to.exist;
done();
}, 500);
});
My babel-rc has dynamic-import-node so running this outside of the test works all fine. But there seems to be no clear/documented way of mocking (with sinon) the results of an import promise. Any ideas?

Invalid Chai property: toMatchSnapshot -- React.js Jest testing

I'm getting an error: Invalid Chai property: toMatchSnapshot when I try to use Jest + Enzyme's snapshot testing. I've updated my React version to 16.2 and I use enzyme-to-json library with Enzyme 3.
Code is below:
import React from 'react';
import ReactDOM from 'react-dom';
import ConnectedApp, { App } from './App';
import { ConnectedRouter } from 'react-router-redux';
import { Provider } from 'react-redux';
import { expect } from 'chai';
import { mount, shallow } from 'enzyme';
import createHistory from 'history/createMemoryHistory'
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import toJson from 'enzyme-to-json';
describe('App tests', () => {
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
let store, container, history, wrapper;
const initialState = {
output: true
}
beforeEach(() => {
store = mockStore(initialState);
history = createHistory();
wrapper = mount(
<Provider store={store}>
<ConnectedRouter history={history}>
<ConnectedApp />
</ConnectedRouter>
</Provider>
)
});
it('+++capturing Snapshot of App', () => {
expect(toJson(wrapper)).toMatchSnapshot();
});
})
I've also tried this with Jest's render like so:
import renderer from 'react-test-renderer';
it('renders correctly', () => {
var component = <Provider store={store}>
<ConnectedRouter history={history}>
<ConnectedApp />
</ConnectedRouter>
</Provider>
const tree = renderer
.create(component)
.toJSON();
expect(tree).toMatchSnapshot();
});
But I still get the Invalid Chai property: toMatchSnapshot error. Anyone know what's up?
This isn't an issue with the renderer you are using. The problem is that you are using chai expectations instead of the expectation library that ships with jest. The chai API has no toMatchSnapshot method. To fix it you can do the following:
Stop using chai and use the jest expectations exclusively. This may simply be a matter of removing line 6: import { expect } from 'chai'
However, if you need to continue to use chai (i.e. you have a lot of chai tests already written and you don't want to do a major overhaul all at once) you can do two things:
Alias either the the chai or jest expect functions in your test setup file e.g. global.chaiExpect = chai.expect
Monkey-patch the global expect function so that you can use both the chai and the jest API like in this blog post: https://medium.com/#RubenOostinga/combining-chai-and-jest-matchers-d12d1ffd0303
The relevant bit is this:
// Make sure chai and jasmine ".not" play nice together
const originalNot = Object.getOwnPropertyDescriptor(chai.Assertion.prototype, 'not').get;
Object.defineProperty(chai.Assertion.prototype, 'not', {
get() {
Object.assign(this, this.assignedNot);
return originalNot.apply(this);
},
set(newNot) {
this.assignedNot = newNot;
return newNot;
},
});
// Combine both jest and chai matchers on expect
const originalExpect = global.expect;
global.expect = (actual) => {
const originalMatchers = originalExpect(actual);
const chaiMatchers = chai.expect(actual);
const combinedMatchers = Object.assign(chaiMatchers, originalMatchers);
return combinedMatchers;
};
For those transitioning away from chai (which would be hijacking expect() from jest in the top level setupTests.js file) the simpler solution is to load jest's expect() again on top of the current test file like so:
import { expect } from "#jest/globals";
That is, until you can't fully do away with any
global.expect = chai.expect;
somewhere in the code, setupTests.js for example.
Its Simple.Just write your test scripts (something.spec.js) in to another file without importing 'chai' . It will work like a charm. No need for messy stuffs.Keep it Simple !
This is partially related to this post & root cause given by other authors are quite accurate and very informative.
I also faced same problem as discussed in this post, when I was trying to use expect(container).toHaveLength(1);
I solved this issue by changing my way to write assertion in Jest way like,
expect(container).to.have.length(1);
So basically we need to find way to change our assertion to write in Jest way, if we are using Jest.
Hope it may help someone.

Resources