Jest

From bibbleWiki
Jump to navigation Jump to search

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.

Example code can be found at [here]

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.

Code Coverage

With Jest we can get a report by typing

npm test  -- --coverage

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

Function to Mock

/**

* Fetch question details from the local proxy API
*/
export function * handleFetchQuestion ({question_id}) {
    const raw = yield fetch(`/api/questions/${question_id}`);
    const json = yield raw.json();
    const question = json.items[0];
    /**
     * Notify application that question has been fetched
     */
    yield put({type:`FETCHED_QUESTION`,question});
}

Mocking the JavaScript Fetch function

In the code below we are attempting to mock the javascript fetch function where call the function and return a value. 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;

Mocking the handleFetchQuestion

Now we need to mock the original function above using our ownversion of fetch. We set the value to be the format expected back from fetch which is an object which we can do because __setValue is not type aware [{"question_id": 42}]

import { handleFetchQuestion }  from './fetch-question-saga';
import fetch from 'isomorphic-fetch';
/**
 * This test is an example of two important Jest testing principles,
 * 1) we're mocking the "fetch" module, so that we don't actually make a request every time we run the test
 *  The module, isomorphic fetch, is conveniently mocked automatically be including the file __mocks__/isomorphic-fetch.js adjacent to to the Node.js folder
 * 2) we're using an async function to automatically deal with the fact that our app isn't synchronous
 */
describe("Fetch questions saga",()=>{
    beforeAll(()=>{
        fetch.__setValue([{question_id:42}]);
    });
    it("should get the questions from the correct endpoint in response to the appropriate action",async ()=>{
        const gen = handleFetchQuestion({question_id:42});
        /**
         * At this point, isomorphic fetch must have been mocked,
         * or an error will occur, or, worse, an unexpected side effect!
         */
        const { value } = await gen.next();
        expect(value).toEqual([{question_id:42}]);

        /**
         * We can also assert that fetch has been called with the values expected (note that we used a spy in the file where we mock fetch.)
         */
        expect(fetch).toHaveBeenCalledWith(`/api/questions/42`);
    });
});

Snapshot Testing

It is really hard to be excited about this. It seems so fragile. Basically it records the first run and makes comparisons on subsequent runs. Any change is flagged.

import React from 'react';
import TagsList from './TagsList'
import renderer from 'react-test-renderer';


describe("The tags list",()=>{
    /**
     * The tagsList can be tested against an expected snapshot value, as in below.
     */
   it ("renders as expected",()=>{
       const tree = renderer
           .create(<TagsList tags={[`css`,`html`,`typescript`,`coffeescript`]}/>)
           .toJSON();

       expect(tree).toMatchSnapshot();
   });
});

Component Testing

See also React_Testing_Components

Making Components More Testable

From best to worse.

  • No internal state - output is a idempotent product of the props
  • No side-effects, have these handled by sagas, thunks etc
  • No lifecycle hooks - fetching data handled on application level

Component Testing with Redux

React and Redux

These a perceived as a great match because it means

  • Components do not generate side effects, only actions
  • Components consist of display and container components
  • No state in Components

So the approach to testing for React Redux Components is

  • Test Container and Display Separately
  • Use Unit test to verify methods and props on container
  • Use snapshot tests for the display

Redux and Testing mapStateToProps

mapStateToProps is the function responsible for mapping state to the props. Our focus on testing here is only the mapping and not the content. i.e. that the state is appropriately mapped to the props.

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);
}

Component Testing with State

Bit of a lot of code but here goes

Create component

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

And here is the 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

Let's make a mocked service in the appropriate place next to the real 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)