Jest
Introduction
This is a page to introduce Jest. Facebook employs a lot of the people who develop jest and can be and is used for test in other frameworks.
Choices
Popular tools for testing are Enzyme and Jasmine/Mocha. Jest is built on top of Jasmine/Mocha. Jest works with or without React and has a great assertion library.
Why Not Enzyme
This has over 200+ issues open and 26 of these are bugs. Jest is recommended by the react team.
Jest vs Mocha
Jest and Mocha both run tests sync and async but jest also has the following features.
- Spies (stubbing methods to spy)
- Snapshot testing
- Module mocking (stubbing objects)
Getting Started
Running
We can run jest on it's own or continually with watch mode
jest --watch
At least one "it" block must be found or jest will return an error.
Naming Test
The file names must be recognized as a test.
__tests__/*.js
*.spec.js
*.test.js
Example Test in Jest
Describe is the suite, it is the test
describe("The question list ", ()=> {
it ("should display a list of items", ()=> {
expect(2+2).toEqual(4);
})
it ("should display a list of items", ()=> {
expect(2+4).toEqual(6);
})
})
Setup and Teardown
This can be done using before each and before all.
BeforeEach BeforeAll
beforeAll(()=>{
console.log("Hello");
});
and to tear down we can use AfterEach AfterAll
afterAll(()=>{
console.log("Hello");
});
Note does not matter what order you write them in.
Skipping and Isolating Tests
Add keyword only to include tests
it.only ("should display a list of items", ()=> {
expect(2+2).toEqual(4);
})
Add keyword skip to exlude tests
it.skip ("should display a list of items", ()=> {
expect(2+2).toEqual(4);
})
Async Tests
// Using a callback that is passed to the test force it to wait
it('async test 1', done=> {
setTimeout(done, 100)
});
// Return a Promise which jest knows to wait for
it('async test 2',()=> {
return new Promise(
resolve => setTimeout(resolve,100)
);
});
// Use await to wait before returning.
it('async test 1', async ()=>
await delay(100)
);
Mocking
Why
- Reduces dependencies and makes testing faster
- Prevents side effects
- Control the scenario we wish to test e.g. timeout, corrupt data
What is Mocking
A mock is a replacement for known functionality which can be used to capture usage of the original function and fake a response to the users of the mock.
Setup
Create directory __mocking__ at the same level as npm node_module you are trying to mock. This makes your module get loaded in place of the original module.
Example
Here is a simple example which mocks the function isomorphicFetch. I orginally struggled with this because of the old syntax but it became a lot clear after so much work with node js and modern javascript
let __value = 42;
// Old syntax
// Replace a function which returns 42
const isomorphicFetch = jest.fn( (){ returns __value });
// Add a function to set the value
isomorphicFetch.__setValue = function(v) {__value = v};
// Modern notation
const isomorphicFetch = jest.fn( ()=> __value);
isomorphicFetch.__setValue = v => __value = v;
export default isomorphicFetch;
And the test case. We import the function to be mocked from out __mocks__/isomorphic-fetch.js
import { handleFetchQuestion } from './fetch-question-saga';
import fetch from 'isomorphic-fetch';
describe("Fetch question s saga", ()=>{
beforeAll( () => {
fetch.__setValue([{question_id:42}] );
});
it ("should fetch the question saga", async ()=> {
const gen = handleFetchQuestion({question_id:42});
const { value } = await gen.next();
expect(value).toEqual( [{question_id:42}] )
expect(fetch).toHaveBeenCalledWith('/api/questions/42');
});
});
Redux and Testing mapStateToProps
You can easily test the map from state to props. e.g.
import { mapStateToProps } from '../QuestionDetail';
it("should map the state to props correctly", () => {
// Sample App State with sampleQuestion being object in state
const sampleQuestion {
question_id:42,
body: "Space is big"
};
const appState = {
questions: [sampleQuestion]
};
// Sample Props
const ownProps = {
question:42
});
const componentState = mapStateToProps(appState, ownProps)
expect(componentState).toEqual(sampleQuestion);
}
Sample test with state
Bit of a lot of code but here goes
Create component to test
import React from 'react';
export default class extends React.Component {
constructor)(...args) {
super(...args);
this.state = {
count: -1
}
}
async comonentDidMount () {
let { count } = await NotificationService.GetNotification();
this.setState( {
count
});
}
render() {
return (
<section className="mt-3 mb-2">
<div className="notifications">
{this.state.count != -1 ? '${this.state.count} Notifications Awaiting!' : 'Loading...'}
</div>
</section>
)
}
}
Create a Service
export default {
async GetNoitifications() {
console.warning("REAL NOTIFICATION SERVICE!");
await delay(1000);
return { count: 42 };
}
}
Test Case
import NotificationsViewer from '../NotificationsViewer';
import renderer from 'react-test-renderer';
import React from 'react';
import delay from 'redux-saga';
// NOTE Local module you need to pass in url of notification service
jest.mock('../../services/NotificationService');
// NOTE 2 this needs to be a require and needs to be after the jest. Making it
// an import will fail
const notificationService = require('../../services/NotificationService').default;
describe("The notification viewer", () => {
beforeAll( ()=> {
notificationService.__setCount(5);
});
it("should display the correct number of notifications", async()=> {
const tree = renderer
.create(
<NoticationsViewer/>
)
await delay();
const instance = tree.root;
// find by className the component
const component = instance.findByProps( { className: 'notificiations' });
const text = component.children[0];
export(text).toEqual("5 Notifications");
});
});
Mocked Service src\services\__mocks__\NotificationService.js
let count = 0;
export default {
__setCount(__count) {
count = _count;
},
async GetNotifications() {
console.warn("Good Job! Using Mock");
}
}
Matchers in Jest
These can be found [1]
Some of them for quick reference.
expect(value) expect.extend(matchers) expect.anything() expect.any(constructor) expect.arrayContaining(array) expect.assertions(number) expect.hasAssertions() expect.not.arrayContaining(array) expect.not.objectContaining(object) expect.not.stringContaining(string) expect.not.stringMatching(string | regexp) expect.objectContaining(object) expect.stringContaining(string) expect.stringMatching(string | regexp) expect.addSnapshotSerializer(serializer)