Skip to content

Instantly share code, notes, and snippets.

@uladzislau-stuk
Last active December 20, 2019 08:28
Show Gist options
  • Save uladzislau-stuk/d325a7e89e57d5bebddea6b48d5ad1ec to your computer and use it in GitHub Desktop.
Save uladzislau-stuk/d325a7e89e57d5bebddea6b48d5ad1ec to your computer and use it in GitHub Desktop.
[Jest, Enzym]
  1. For components wrapped in HOCs we usually recommend exporting the unwrapped component and mocking any props that the HOC is providing. If you want to test the HOC itself you are allowed to query props on a child component, which should be enough if you're using composition.

What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing

  1. Defining the correct order of components’ testing based on project structure Order for test coverage: Independent -> Auxiliary -> Simple -> Complex (From simple to complex)
  2. Define what should be omitted in test coverage
  3. Snapshot and component logic testing

Main instructions for component testing

  1. One component - one snapshot (there are exceptions when you need to test the behavior of a component in two states; for example, in the state of the component before opening the pop-up and after opening. However, even such variant can always be replaced by this one: the first test stores default state of the component without popup in snapshot, and the second test simulates event and checks the presence of a particular class. In this way, you can easily bypass the creation of several snapshots.)
  2. Testing props ?
  3. Tesing data types ?
  4. Event testing mock event => simulate it => expect event was called mock event => simulate event with params => expect event was called with passed params pass necessary props => render component => simulate event => expect a certain behavior on called event
  5. Testing conditions
  6. State testing The first one checks current state. The second one checks state after calling event. Render component => call function directly in the test => check how state has changed. To call function of the component, you need to get an instance of the component and only then call its methods (example is shown in next test). After you walk through this list of instructions, your component will be covered from 90 to 100%. I leave 10% for special cases that were not described in the article, but can occur in the code.

Mock functions

Goal mock function - is to replace something we don’t control with something we can, so it’s important that what we replace it with has all the features we need.

test('return undefined by default', () => {
	const mock = jest.fn()
    
    let result = mock('foo')
    
    expect(result).toBeUndefined();
  	expect(mock).toHaveBeenCalled();
  	expect(mock).toHaveBeenCalledTimes(1);
  	expect(mock).toHaveBeenCalledWith("foo");
})

// can change
// - return value 
// - implementation 
// - promise resolution
test("mock implementation", () => {
  const mock = jest.fn(() => "bar");

  expect(mock("foo")).toBe("bar");
  expect(mock).toHaveBeenCalledWith("foo");
});

test("also mock implementation", () => {
  const mock = jest.fn().mockImplementation(() => "bar");

  expect(mock("foo")).toBe("bar");
  expect(mock).toHaveBeenCalledWith("foo");
});

test("mock implementation one time", () => {
  const mock = jest.fn().mockImplementationOnce(() => "bar");

  expect(mock("foo")).toBe("bar");
  expect(mock).toHaveBeenCalledWith("foo");

  expect(mock("baz")).toBe(undefined);
  expect(mock).toHaveBeenCalledWith("baz");
});

test("mock return value", () => {
  const mock = jest.fn();
  mock.mockReturnValue("bar");

  expect(mock("foo")).toBe("bar");
  expect(mock).toHaveBeenCalledWith("foo");
});

Dependency injection

One of the common ways to use the Mock Function is by passing it directly as an argument to the function you are testing. This allows you to run your test subject, then assert how the mock was called and with what arguments:

const doAdd = (a, b, callback) => {
  callback(a + b);
};

test("calls callback with arguments added", () => {
  const mockCallback = jest.fn();
  doAdd(1, 2, mockCallback);
  expect(mockCallback).toHaveBeenCalledWith(3);
});

This strategy is solid, but it requires that your code supports dependency injection. Often that is not the case, so we will need tools to mock existing modules and functions instead.

Mocking Modules and Functions

There are three main types of module and function mocking in Jest:

  • jest.fn: Mock a function
  • jest.mock: Mock a module
  • jest.spyOn: Spy or mock a function

Approaches:

  1. reassign a function to the Mock Function (examples above)
  2. use jest.mock to automatically set all exports of a module to the Mock Function
  3. keep the original implementation, watch method calls

jest.mock

// math.js
export const add      = (a, b) => a + b;
export const subtract = (a, b) => b - a;

// math.test.js
jest.mock('./math.js') 
// make math.js
// export const add = jest.fn()
// export const substract = jest.fn()

The only disadvantage of this strategy is that it’s difficult to access the original implementation of the module. For those use cases, you can use spyOn.

jest.spyOn Here we simply “spy” calls to the math function, but leave the original implementation in place:

import * as app from "./app";
import * as math from "./math";

test("calls math.add", () => {
  const addMock = jest.spyOn(math, "add");

  // calls the original implementation
  expect(app.doAdd(1, 2)).toEqual(3);

  // and the spy stores the calls to add
  expect(addMock).toHaveBeenCalledWith(1, 2);

This is useful in a number of scenarios where you want to assert that certain side-effects happen without actually replacing them.

In other cases, you may want to mock a function, but then restore the original implementation:

test("calls math.add", () => {
  const addMock = jest.spyOn(math, "add");

  // override the implementation
  addMock.mockImplementation(() => "mock");
  expect(app.doAdd(1, 2)).toEqual("mock");

  // restore the original implementation
  addMock.mockRestore();

The key thing to remember about jest.spyOn is that it is just sugar for the basic jest.fn() usage. We can achieve the same goal by storing the original implementation, setting the mock implementation to to original, and re-assigning the original later:

test("calls math.add", () => {
  // store the original implementation
  const originalAdd = math.add;

  // mock add with the original implementation
  math.add = jest.fn(originalAdd);

  // spy the calls to add
  expect(app.doAdd(1, 2)).toEqual(3);
  expect(math.add).toHaveBeenCalledWith(1, 2);

  // override the implementation
  math.add.mockImplementation(() => "mock");
  expect(app.doAdd(1, 2)).toEqual("mock");
  expect(math.add).toHaveBeenCalledWith(1, 2);

Tricks

Enzyme

Get current state

// you can use both options
console.log(wrapper.state())
console.log(instance.state)
console.log(instance.props)

You should know

WS

You might have noticed that some of the global Jest methods (like describe and beforeEach) in JavaScript files are marked as unresolved in the editor. To fix that, install the TypeScript type definition files for Jest: Go to Preferences | Languages & Frameworks | JavaScript | Libraries, click Download on the right-hand side of the dialog, then search for Jest in the list and click Install. Or add @types/jest to devDependencies in project’s package.json.

Great articles

Understanding Jest Mocks

Testing terminology

Testing asynchronous lifecycle methods

Best Practices when testing with Jest and Enzyme

https://medium.com/codeclan/testing-react-with-jest-and-enzyme-20505fec4675

Testing with Jest in WebStorm

Continuously testing React applications with Jest and Enzyme

What and How to Test with Jest and Enzyme. Full Instruction on React Components Testing

Resources

Airbnb Enzym API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment