DEPRECATED This project is unmantained, deprecated and goes into archive with followup removal in 2 years.
How to write unit tests in our current frontend stack.
This project is built with @mercateo/ws
. We assume you use an alias like alias ws="npm run -s ws --"
to use it.
To build and serve the app call ws watch
. To run the unit tests call ws unit
.
Here is a breakdown of all involved frameworks:
expect
: Our assertion framework (e.g.expect(foo).toBe(bar)
). Includes support for spies.- Mocha: Our test framework responsible for structuring (e.g.
decribe
,it
) and reporting. - Karma: Our test runner which takes care of launching browsers.
react-test-renderer
(andreact-dom/test-utils
): A lib for basic React tests. (react-addons-test-utils
is deprecated.)- Enzyme: A lib for more complex React tests.
- InversifyJS: Used for dependency injection/inversion of control in some of our packages.
- PhantomJS: A headless browser which is used to execute the tests. (It is based on WebKit and has the same APIs as a real browser, but starts faster.)
import-inject-loader
: A WebPack loader which can add new APIs to your modules at build time, so you can overrideimport
's or globals with mocks in your tests.
Note that we don't really use a mocking library in our stack. In most cases this is not necessary due to the dynamic nature of JavaScript. We just write down what we need. However @otbe experimented with writing a TypeScript specific mocking library called emock
. It was quite nice, but we don't really used it so far.
In src/
you can find basic components written with our current stack: React, MobX and TypeScript.
src/component.tsx
exports<Add />
. This is a stateless functional component which uses the functionadd
fromsrc/function.ts
.src/component-with-add-prop.tsx
exports<Add2 />
. This is the same component as<Add />
, butadd
can be overwritten viaprops
.src/component-with-interaction.tsx
exports<Counter />
. This is a stateful class component to showcase testing of dynamic state based on user interactions.src/component-with-fetching.tsx
exports<FetchUser />
. This stateful class component showcases the testing of components which request data.src/component-with-fetching-2.tsx
andsrc/component-with-fetching-di.tsx
are variations of<FetchUser />
similar to like<Add2 />
is a variation of<Add />
to makefetch
overwriteable.
In tests/
you can find the according tests:
tests/function.ts
shows you the most basic test of a JavaScript function.tests/component-without-enzyme.tsx
shows you a basictest of a React component with'react-addons-test-utils'
. The same test written with'enzyme'
can be found intests/component-with-enzyme.tsx
.tests/component-with-spy.tsx
shows you how to use spies.tests/component-with-interaction.tsx
shows you how to fake user interactions.tests/component-with-fetching.tsx
,tests/component-with-fetching-2.tsx
andtests/component-with-fetching-di.tsx
shows you how to mock http requests via re-assigningfetch
or with dependency injection.tests/component-with-import-inject-loader.tsx
shows the usage ofimport-inject-loader
in the components<FetchUser />
and<Add />
.
To run a single test write it.only(...)
instead of it(...)
. To ignore a single test write it.skip(...)
. The same is true for test suites created with describe
(e.g. describe.only
and describe.skip
).
PhantomJS is based on Chrome and like other browsers PhantomJS becomes old. That's why we typically need similar polyfills in our tests like we use in our real app (e.g. babel-polyfill
for Object.assign
).
For primitive values there is no real difference between expect(foo).toBe(bar)
and expect(foo).toEqual(bar)
. toBe
means "the same" and is like a strict comparison with ===
. E.g. expect(foo).toBe(bar)
is the same as expect(foo === bar).toBe(true)
. On the other hand toEqual
checks if two objects look alike - it compares all keys and values in the object with each other. I'd default to use toBe
as it is more strict.
The line numbers in the stack trace are currently allways a little bit off... that happens because of incorrect source maps. But the file name should be allways correct.
Many people prefer Jest nowadays. It's fast, because it is only run inside a Node environment - not inside a browser engine. So you have to mock all Browser APIs (e.g. the DOM). Of course most people have done this already in open source projects. But it feels odd to test your code inside a different environment where it will be executed by the user.
Jest features Snapshot testing. This can be usefull for testing something liek complex React trees. You capture a know good result and use it for test to see, if your result changes anywhere. However this tests really everything and some things are likely to change often (like styling), so it could be possible that you would need to update your snapshots very often - even if your test is not interested in styling differences. However... this can be avoided, if you write custom serializers. At the end it depends on your use case.
Another interesting approach is html-looks-like
which only tests what "looks like" something.