Writing unit tests for your SharePoint Framework components

April 5, 2017

Last week I published a sample project on GitHub which shows how you can test your SharePoint Framework components with some predefined unit tests.

Info: here is a link to the repo - https://github.com/estruyf/spfx-testing-wp

The current sample is created with React. The unit tests are created only to test the actual component instead of the main web part.

Info: At this moment, there is a dependency issue when you try to create unit tests for the actual web part. Read more about this here: https://github.com/SharePoint/sp-dev-docs/issues/526

Now, when you are working with React and components, it actually makes more sense to do intensive testing of the components itself. This gives you much more control over the component tests and the outcomes you want to test. For example, you can change the state, trigger function calls, faking click events and more. Of course, once the dependency issue is gets fixed, you can also write some tests for the actual web part.

If you want to write some test yourself, you will have to go through a couple of steps which will be explained in this article.

First some configuration

When you created a new SPFx project, you automatically get a tests folder in the web part folder.

Tests folder located in your project
Tests folder located in your project

In the folder, you find a sample test file: .test.ts. The first thing you will have to do when you want to test react components is changing the ts extension to tsx. This allows you to load the components in JSX syntax.

Sample test file
Sample test file

Installing a couple of developer dependencies

Once you changed the file extension of the test file. You have to install the following addition developer dependencies:

Enzyme is a set of test utilities created for React (developed by Airbnb) and requires the react-addons-test-utils. Enzyme makes it easy to assert, manipulate your components’ output.

To install these two dependencies, execute the following command: npm install enzyme react-addons-test-utils enzyme-adapter-react-15 react-testrenderer --save-dev --save-exact

Writing tests for your React components

Now you are ready to write some tests. Override the contents of the test file, with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <reference types="mocha" />
/// <reference types="sinon" />

import * as React from 'react';
import { assert, expect } from 'chai';
import { mount, configure } from 'enzyme';
import HelloWorld from '../components/HelloWorld';

configure({ adapter: new Adapter() });

declare const sinon;

describe('<HelloWorld />', () => {
    const descTxt = "TestingThisOneOut";
    let componentDidMountSpy;
    let renderedElement;

    before(() => {
        componentDidMountSpy = sinon.spy(HelloWorld.prototype, 'componentDidMount');
        renderedElement = mount(<HelloWorld description={descTxt} />);
    });

    after(() => {
        componentDidMountSpy.restore();
    });

    // Write your tests here
});

The code you see above does not contain any tests yet. The before function is the first once which gets called and it loads the component.

At the end of the tests, the after function restores all components.

You can also do something before and after each test by writing beforeEach and afterEach functions.

Tests are written by it() (it should do something) functions. Here are a couple of examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
it('<HelloWorld /> should render something', () => {
    // Check if the component contains a paragraph element
    expect(renderedElement.find('p')).to.be.exist;
});

it('<HelloWorld /> should render the description', () => {
    // Check if the component renders the description you provided
    expect(renderedElement.find('p.description').text()).to.be.equals(descTxt);
});

it('<HelloWorld /> should render an ul', () => {
    // Check if the component contains an UL element
    expect(renderedElement.find('ul')).to.be.exist;
});

it('<HelloWorld /> state results should be null', () => {
    // State should be null when the component gets loaded
    expect(renderedElement.state('results')).to.be.null;
});

it('<HelloWorld /> should call componentDidMount only once', () => {
    // Check if the componentDidMount is called once
    expect(componentDidMountSpy.calledOnce).to.equal(true);
});

it('<HelloWorld /> should render an ul with 3 items (using the mock data)', (done) => {
    // New instance should be created for this test due to setTimeout
    // If the global renderedElement used, the result of "ul li"" will be 10 instead of 3
    // because the state changes to 10 in the last test and
    // the last test is executed before this one bacause of setTimeout
    let renderedElement1 = mount(<HelloWorld description={descTxt} />);
    // Wait for 1 second to check if your mock results are retrieved
    setTimeout(() => {
        // Trigger state update
        renderedElement1.update();
        expect(renderedElement1.state('results')).to.not.be.null;
        expect(renderedElement1.find('ul li').length).to.be.equal(3);
        done(); // done is required by mocha, otherwise the test will yield SUCCESS no matter of the expect cases
    }, 1000);
});

it('<HelloWorld /> should render 10 list items (triggering setState from the test)', () => {
    // Change the state of the component
    renderedElement.setState({
        results: {
            value: [
                { Title: "Mock List 1", Id: '1' },
                { Title: 'Mock List 2', Id: '2' },
                { Title: 'Mock List 3', Id: '3' },
                { Title: 'Mock List 4', Id: '4' },
                { Title: 'Mock List 5', Id: '5' },
                { Title: 'Mock List 6', Id: '6' },
                { Title: 'Mock List 7', Id: '7' },
                { Title: 'Mock List 8', Id: '8' },
                { Title: 'Mock List 9', Id: '9' },
                { Title: 'Mock List 10', Id: '10' }
            ]
        }
    });

    expect(renderedElement.update().state('results')).to.not.be.null;
    expect(renderedElement.update().find('ul li').length).to.be.equal(10);
});

More information about JavaScript unit testing;

Running your tests

You can run your tests via this command: gulp test. When you run the command with the sample project, you should get the following result:

Sample component tests outcome
Sample component tests outcome

Info: the sample web part also contains a mock HTTP client which simulates an async call to retrieve items from a list. The web part renders the following output;

Sample web part rendering
Sample web part rendering

Comments

comments powered by Disqus