1# Unit testing
2
3## Overview
4
5Our unit tests in Activity Stream are written with mocha, chai, and sinon, and run
6with karma. They include unit tests for both content code (React components, etc.)
7and `.jsm`s.
8
9You can find unit tests in `tests/unit`.
10
11## Execution
12
13To run the unit tests once, execute `npm test`.
14
15To run unit tests continuously (i.e. in "test-driven development" mode), you can
16run `npm run tddmc`.
17
18## Debugging
19
20To debug tests, you should run them in continuous mode with `npm run tddmc`.
21In the Firefox window that is opened (it should say "Karma... - connected"),
22click the "debug" button and open your console to see test output, set
23breakpoints, etc.
24
25Unfortunately, source maps for tests do not currently work in Firefox. If you need
26to see line numbers, you can run the tests with Chrome by running
27`npm install --save-dev karma-chrome-launcher && npm run tddmc -- --browsers Chrome`
28
29## Where to put new tests
30
31If you are creating a new test, add it to a subdirectory of the `tests/unit`
32that corresponds to the file you are testing. Tests should end with `.test.js` or
33`.test.jsx` if the test includes any jsx.
34
35For example, if the file you are testing is `lib/Foo.jsm`, the test
36file should be `test/unit/lib/Foo.test.js`
37
38## Mocha tests
39
40All our unit tests are written with [mocha](https://mochajs.org), which injects
41globals like `describe`, `it`, `beforeEach`, and others. It can be used to write
42synchronous or asynchronous tests:
43
44```js
45describe("FooModule", () => {
46  // A synchronous test
47  it("should create an instance", () => {
48    assert.instanceOf(new FooModule(), FooModule);
49  });
50  describe("#meaningOfLife", () => {
51    // An asynchronous test
52    it("should eventually get the meaning of life", async () => {
53      const foo = new FooModule();
54      const result = await foo.meaningOfLife();
55      assert.equal(result, 42);
56    });
57  });
58});
59```
60
61## Assertions
62
63To write assertions, use the globally available `assert` object (this is provided
64by karma-chai, so you do not need to `require` it).
65
66For example:
67
68```js
69assert.equal(foo, 3);
70assert.propertyVal(someObj, "foo", 3);
71assert.calledOnce(someStub);
72```
73
74You can use any of the assertions from:
75
76- [`chai`](http://chaijs.com/api/assert/).
77- [`sinon-chai`](https://github.com/domenic/sinon-chai#assertions)
78
79### Custom assertions
80
81We have some custom assertions for checking various types of actions:
82
83#### `.isUserEventAction(action)`
84
85Asserts that a given `action` is a valid User Event, i.e. that it contains only
86expected/valid properties for User Events in Activity Stream.
87
88```js
89// This will pass
90assert.isUserEventAction(ac.UserEvent({event: "CLICK"}));
91
92// This will fail
93assert.isUserEventAction({type: "FOO"});
94
95// This will fail because BLOOP is not a valid event type
96assert.isUserEventAction(ac.UserEvent({event: "BLOOP"}));
97```
98
99## Overriding globals in `.jsm`s
100
101Most `.jsm`s you will be testing use `Cu.import` or `XPCOMUtils` to inject globals.
102In order to add mocks/stubs/fakes for these globals, you should use the `GlobalOverrider`
103utility in `test/unit/utils`:
104
105```js
106const {GlobalOverrider} = require("test/unit/utils");
107describe("MyModule", () => {
108  let globals;
109  let sandbox;
110  beforeEach(() => {
111    globals = new GlobalOverrider();
112    sandbox = globals.sandbox; // this is a sinon sandbox
113    // This will inject a "AboutNewTab" global before each test
114    globals.set("AboutNewTab", {override: sandbox.stub()});
115  });
116  // globals.restore() clears any globals you added as well as the sinon sandbox
117  afterEach(() => globals.restore());
118});
119```
120
121## Testing React components
122
123You should use the [enzyme](https://github.com/airbnb/enzyme) suite of test utilities
124to test React Components for Activity Stream.
125
126Where possible, use the [shallow rendering method](https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md) (this will avoid unnecessarily
127rendering child components):
128
129```js
130const React = require("react");
131const {shallow} = require("enzyme");
132
133describe("<Foo>", () => {
134  it("should be hidden by default", () => {
135    const wrapper = shallow(<Foo />);
136    assert.isTrue(wrapper.find(".wrapper").props().hidden);
137  });
138});
139```
140
141If you need to, you can also do [Full DOM rendering](https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md)
142with enzyme's `mount` utility.
143
144```js
145const React = require("react");
146const {mount} = require("enzyme");
147...
148const wrapper = mount(<Foo />);
149```
150