Course Notes: JavaScript Testing Practices and Principles
Here are the notes I took when working through the JavaScript Testing Practices and Principles course by Kent C. Dodds.
Testing in general
These are some key testing takeaways I gained from this course.
Don't waste time
TDD usually isn’t great for UI development or development of anything that will change often.
Kent spends most of his time on integration tests because e2e tests tend to be expensive and slow. However, tooling is getting better and failing e2e tests are usually the most costly to the user. Cypress is a promising tool for e2e testing. Cypress runs in context of the browser which makes a strong case against the Page Object Model which was nice for Selenium tests with no browser context.
Think of other developers
Your tests should work but also express intent. Future developers need to know what is important in your test implementation.
Example #1:
const userToTest = myPotentiallyUnsafeUser;// assert userToTest is safe to be serialized to JSON
vs
const safeUser = { id: 1, name: "Jane" };const userToTest = {...safeUser,myPotentiallyUnsafeData};// assert userToTest is safe to be serialized to JSON
Example #2:
const objectToTest = {id: 1,type: "mock",...otherUnimportantProps, // take up space and increase noise, especially when repeatedimportantPropForTest: true};// assert something about objectToTest
vs
const objectToTest = baseObjectWithUnimportantProps;objectToTest.importantPropForTest = true;// assert something about objectToTest
Think of your users
You want to test from the user's perspective.
- Avoid testing implementation details.
- Test the way users will consume your app.
- When co-locating tests make sure not to ship your tests to your users.
Jest-specific Notes
This course was full of Jest goodies. Here are some that stuck out to me.
Mocking
I found it helps to think of jest.mock
as an entirely new module. It will swap out whatever import there is with what is returned. Under the hood, Jest takes control of the module system in Node forcing imports to go through the Jest registry first.
When mocking a module, you can import the actual module if you need the real thing for whatever reason using the jest.requireActual
function. Here's an example from the jest.requireActual
docs of partially mocking a module.
jest.mock("../myModule", () => {// Require the original module to not be mocked...const originalModule = jest.requireActual(moduleName);return {__esModule: true, // Use it when dealing with esModules...originalModule,getRandom: jest.fn().mockReturnValue(10)};});const getRandom = require("../myModule").getRandom;getRandom(); // Always returns 10
Another neat thing Jest allows you to do is mock an entire module for all tests by creating a __mocks__
directory as a sibling to the module(s) to test and including the mock implementation in a new file inside the __mocks__
directory with the same name as the module you're mocking. Now Jest will import from that mock file rather than the real module throughout your tests where you use jest.mock(<pathToRealModule>)
. You can also mock node_modules
by creating a __mocks__
folder in the root directory of your workspace and following this pattern.
Also, to facilitate tests running in total isolation from each other you can clear a mock in beforeEach
using myMockedFn.mockClear()
. For example, without this your tests would be failing if you're asserting myMockedFn
was called a certain number of times in multiple tests.
Assertion Functions
toBe
compares objects by reference.
expect({}).not.toBe({});
toEqual
compares objects by value.
expect({}).toEqual({});
toMatchObject
is similar to toEqual
, but for partial equality.