-
Notifications
You must be signed in to change notification settings - Fork 3
How To: Write a unit test in javascript
Jest: https://jestjs.io/ Enzyme: https://github.com/airbnb/enzyme
Basic javascript testing only requires a knowledge of Jest.
- Create a file with the same name and location as your component, but with a test suffix before the .js suffix (ex: MyComponent.test.js)
- Import everything that you will be testing
- Optional: Use globals to do stuff before tests are run. You can see what each global does here: https://jestjs.io/docs/en/api 4.Create a test suite
describe('This is my test suite', () => {
// All tests for this suite go into this area
});
5.Optional: Auto generate mocks. You can find Jests mock API here: https://jestjs.io/docs/en/mock-function-api 6. Create a test and use expect() for test validation. You can find the API of expect here: https://jestjs.io/docs/en/expect
it('Does stuff to foo', () => {
const mockFoo = {bar: 'Mocked bar'};
let expectedReturn = {bar: 'Bar has been changed'}
let actualReturn = MyImport.doesStuffToFoo(mockFoo);
expect(actualReturn).toEqual(expectedReturn);
});
- Add more tests until the test suite is complete, then add more test suites until thoroughly tested.
- Create a file with the same name and location as your component, but with a test suffix before the .js suffix (ex: MyComponent.test.jsx)
- Add the above code to set up imports
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow, mount, render, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import <MyComponent> from './<MyComponent>';
configure({ adapter: new Adapter() });
- Add a test suite for each component being tested
describe('<MyComponent />', () => {
// All tests for <MyComponent /> go here
});
- Render your component within each test. Mount does full DOM rendering. Use it for testing lifecycle methods in hooks and integration with DOM API's. Documentation: https://airbnb.io/enzyme/docs/api/mount.html
it('calls componentDidMount', () => {
sinon.spy(Foo.prototype, 'componentDidMount');
const wrapper = mount(<Foo />);
expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
});
The material-ui library contains utilities to support testing, see https://material-ui.com/guides/testing/. Because most components we develop using material-ui are going to wrap the component with the WithStyles HOC then shallow doesn't create a wrapper with the component. Enzyme supports dive() which goes down one more level into the component hierarchy. To simplify this the createShallow() material-ui util takes a prop "{ dive: true }" which wraps the result of calling dive() on the test component. createMount can also be used.
import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import MaterialButton from '@material-ui/core/Button';
import { createShallow } from '@material-ui/core/test-utils';
import Button from '../Button';
configure({ adapter: new Adapter() });
describe('<Button />', () => {
let shallow;
beforeAll(() => {
shallow = createShallow({ dive: true });
});
it('contains a material button', () => {
const wrapper = shallow(<Button buttonType="save" />);
expect(wrapper.find(MaterialButton).length).toBe(1);
});
});
There are 3 ways to handle promises for testing in jest. None are particularly better than the others and they can be mixed and matched. I'd suggest using the first 1 as it allows testing multiple promises within the same test.
The below block is the code being tested in each case.
const someService = {
getStuff: async () => {
try {
const response = await WebRequest.get('SomeService', {
parameterOne: 1
});
const payload = WebRequest.parseDmasAPIResponse(response);
return payload;
} catch(error) {
throw error;
}
}
}
export default someService;
import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import someService from '../someService';
jest.mock('util/WebRequest');
configure({ adapter: new Adapter() });
const response = {
data: {
statusCode: 0,
payload: {
stuff: '1',
},
},
};
const errorMessage = new Error('Palantir 404 error');
describe('someService', () => {
beforeAll(() => {
jest
.spyOn(WebRequest, 'parseDmasAPIResponse')
.mockImplementation(() => response.data.payload);
});
it('Performs a get successfully', async () => {
jest
.spyOn(WebRequest, 'get')
.mockImplementation(() => Promise.resolve(response));
await expect(someService.getStuff()).resolves.toBe(
response.data.payload
);
});
it('Catches errors on get', async () => {
jest
.spyOn(WebRequest, 'get')
.mockImplementation((url, params) => Promise.reject(errorMessage));
await expect(
someService.getStuff()
).rejects.toBe(errorMessage);
});
});
import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import someService from '../SomeService';
jest.mock('util/WebRequest');
configure({ adapter: new Adapter() });
const response = {
data: {
statusCode: 0,
payload: {
stuff: '1',
},
},
};
const errorMessage = new Error('Palantir 404 error');
describe('someService', () => {
beforeAll(() => {
jest
.spyOn(WebRequest, 'parseDmasAPIResponse')
.mockImplementation(() => response.data.payload);
});
it('Performs a get successfully', async () => {
jest
.spyOn(WebRequest, 'get')
.mockImplementation(() => Promise.resolve(response));
return someService.getStuff().then(payload => {
expect(payload.stuff).toBe(1)
})
.catch(error => {
throw error;
}
});
it('Catches errors on get', async () => {
jest
.spyOn(WebRequest, 'get')
.mockImplementation((url, params) => Promise.reject(errorMessage));
return someService
.getStuff()
.catch(error => {
expect(error).toMatch(errorMessage);
}
});
});
This is common with hooks useEffect function and setting the state not updating correctly.
class SomeClass extends Component {
ComponentDidMount() {
someService.getStuff().then(payload => {
// Do something with payload
})
.catch(error) {
throw error;
}
}
render() {
return <div />;
}
}
The above code doesn't return a promise and is not asynchronous so the previous methods won't work. In our test code we can use flushPromises() to force the Promise to resolve before our next step.
// enzyme and class imports
import flushPromises from 'flush-promises';
it('some service call does something', async () => {
jest
.spyOn(someService, 'getStuff')
.mockImplementation(jest.fn(() => Promise.resolve({stuff: 1})));
const wrapper = mount(<SomeClass />);
// wait for next async event to complete.
await flushPromises();
expect(someService.getStuff).toHaveBeenCalled();
});