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

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.

Related

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.

Client.abort is not a function

I am getting below error while run test cases by using jest with react testing library
import React from 'react';
import { render} from '#testing-library/react';
import Somecomponent from './Somecomponent';
import { Provider } from 'react-redux';
import store from './store';
describe('<Somecomponent /> spec', () => {
it('renders the component', () => {
const container = render(
<Provider store={store}>
<Somecomponent />
</Provider>
);
expect(container.firstChild).toMatchSnapshot();
});
});
Actual Result:
Test suite failed to run
TypeError: client.abort is not a function
at XMLHttpRequest.abort (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:379:16)
at Object.abort (node_modules/jsdom/lib/jsdom/living/xhr-utils.js:425:13)
at RequestManager.close (node_modules/jsdom/lib/jsdom/living/nodes/Document-impl.js:133:21)
at Window.close (node_modules/jsdom/lib/jsdom/browser/Window.js:513:29)
I might be a little late to the party, but I found my solution here (in my case the error also occurred in Angular application). Adding the HttpClientTestingModule ensures it's not doing actual http requests in test cases.

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?

Mock basename in Jest / Enzyme

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',
}

Typescript, Jest and i18next: Can't see text in React component

I am testing a React component that uses i18next for internationalization.
The component:
import * as React from "react";
import { t } from "i18next";
export function Hello(_props) {
return <div className="widget-header">
{t("Hello, world!")}
</div>;
}
The test:
import * as React from "react";
import { render } from "enzyme";
import { Hello } from "./hello";
describe("<Hello />", () => {
it("says hi", () => {
let hi = render(<Hello />);
// FAILS!
// Expected "" to contain "Hello"
expect(hi.text()).toContain("Hello");
});
})
My suspicion is that Jest is stubbing i18next.t() to return undefined rather than "Hello, world!", but I am unsure of that.
I have tried to unmock("i18next") without any luck.
How do I write tests for i18next components in Jest?
UPDATE: I was using Typescript as my compiler. It appears that there is a hoisting issue with the loader I use for Typescript (ES6 hoists imports, jest.unmock is not- Babel users have a plugin to handle this).
Not sure why its not working but you can just mock put i18next like this:
jest.mock('i18next', () => ({
t: (i) => i
}))

Resources