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