summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md
blob: c3dd369a2dd48dd68edee84ec46f9b5d9803bd4f (plain)
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# Unit testing

## Overview

Our unit tests in Activity Stream are written with mocha, chai, and sinon, and run
with karma. They include unit tests for both content code (React components, etc.)
and `.jsm`s.

You can find unit tests in `tests/unit`.

## Execution

To run the unit tests once, execute `npm test`.

To run unit tests continuously (i.e. in "test-driven development" mode), you can
run `npm run tddmc`.

## Debugging

To debug tests, you should run them in continuous mode with `npm run tddmc`.
In the Firefox window that is opened (it should say "Karma... - connected"),
click the "debug" button and open your console to see test output, set
breakpoints, etc.

Unfortunately, source maps for tests do not currently work in Firefox. If you need
to see line numbers, you can run the tests with Chrome by running
`npm install --save-dev karma-chrome-launcher && npm run tddmc -- --browsers Chrome`

## Where to put new tests

If you are creating a new test, add it to a subdirectory of the `tests/unit`
that corresponds to the file you are testing. Tests should end with `.test.js` or
`.test.jsx` if the test includes any jsx.

For example, if the file you are testing is `lib/Foo.jsm`, the test
file should be `test/unit/lib/Foo.test.js`

## Mocha tests

All our unit tests are written with [mocha](https://mochajs.org), which injects
globals like `describe`, `it`, `beforeEach`, and others. It can be used to write
synchronous or asynchronous tests:

```js
describe("FooModule", () => {
  // A synchronous test
  it("should create an instance", () => {
    assert.instanceOf(new FooModule(), FooModule);
  });
  describe("#meaningOfLife", () => {
    // An asynchronous test
    it("should eventually get the meaning of life", async () => {
      const foo = new FooModule();
      const result = await foo.meaningOfLife();
      assert.equal(result, 42);
    });
  });
});
```

## Assertions

To write assertions, use the globally available `assert` object (this is provided
by karma-chai, so you do not need to `require` it).

For example:

```js
assert.equal(foo, 3);
assert.propertyVal(someObj, "foo", 3);
assert.calledOnce(someStub);
```

You can use any of the assertions from:

- [`chai`](http://chaijs.com/api/assert/).
- [`sinon-chai`](https://github.com/domenic/sinon-chai#assertions)

### Custom assertions

We have some custom assertions for checking various types of actions:

#### `.isUserEventAction(action)`

Asserts that a given `action` is a valid User Event, i.e. that it contains only
expected/valid properties for User Events in Activity Stream.

```js
// This will pass
assert.isUserEventAction(ac.UserEvent({event: "CLICK"}));

// This will fail
assert.isUserEventAction({type: "FOO"});

// This will fail because BLOOP is not a valid event type
assert.isUserEventAction(ac.UserEvent({event: "BLOOP"}));
```

## Overriding globals in `.jsm`s

Most `.jsm`s you will be testing use `Cu.import` or `XPCOMUtils` to inject globals.
In order to add mocks/stubs/fakes for these globals, you should use the `GlobalOverrider`
utility in `test/unit/utils`:

```js
const {GlobalOverrider} = require("test/unit/utils");
describe("MyModule", () => {
  let globals;
  let sandbox;
  beforeEach(() => {
    globals = new GlobalOverrider();
    sandbox = globals.sandbox; // this is a sinon sandbox
    // This will inject a "AboutNewTab" global before each test
    globals.set("AboutNewTab", {override: sandbox.stub()});
  });
  // globals.restore() clears any globals you added as well as the sinon sandbox
  afterEach(() => globals.restore());
});
```

## Testing React components

You should use the [enzyme](https://github.com/airbnb/enzyme) suite of test utilities
to test React Components for Activity Stream.

Where possible, use the [shallow rendering method](https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md) (this will avoid unnecessarily
rendering child components):

```js
const React = require("react");
const {shallow} = require("enzyme");

describe("<Foo>", () => {
  it("should be hidden by default", () => {
    const wrapper = shallow(<Foo />);
    assert.isTrue(wrapper.find(".wrapper").props().hidden);
  });
});
```

If you need to, you can also do [Full DOM rendering](https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md)
with enzyme's `mount` utility.

```js
const React = require("react");
const {mount} = require("enzyme");
...
const wrapper = mount(<Foo />);
```