Automasean

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 repeated
importantPropForTest: 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.