hard to test. We’re going to look at how to write a test or two. But, we’re also going to look at how to write code that’s easy—or at least easier—to test. And, we’ll look at how to test code that’s hard-to-test.
I’m not going to assume you’ve written a single test before in your life. We’ll test some components that happen to be written in a framework like React or Svelte. But, I don’t expect that you’ll have any familiarity with any of these. As long as you know some JavaScript, have Node installed, and get around the command line—we should be good.
way. And I’m going to hassle everyone that they’re not writing enough tests and I’m never going to approve a PR ever again and I heard that Jeff Bezos said that the build shouldn’t pass if the test coverage drops and I’d like to argue you about the difference between unit, integration, and end-to-end tests. Do you even know what it means to be idempotent? Who cares is all of my code is unreadable? Did I tell you I’ve writing my own functional programming language? Stage Four: The Danger Zone
either you or it’s your users. And if it’s you, then you’re either doing it manually every time you make a change—or, you have an automated system in place. My job is to help you get that automated system in place.
by writing tests—not talking about it. A lot of the content around testing has a tendency to get really philosophical. We’re going to focus on learning how to write tests by writing tests. And, we’ll touch upon all of that other stu ff as we go along.
code breaks—not mine. It would be great if we only hard to worry about when things go exactly as planned. But, they don’t always do that and we need to be prepared for that.
tests with this one weird trick. Are you going to use these tricks every day? Probably not. Do I expect you to memorize them? I don’t. I just want you to be aware that they exist if you need them.
reality of being a front-end engineer. I’ve been told that a lot of JavaScript developers work on UIs. I’ve been told that browser’s have this thing called the DOM. We should probably test that.
is more things than I’d like, frankly. I’ve also heard that some of these web applications make network requests. Did you know you can install packages from npm? How do we test this kind of stu ff ?
a topic in it’s own right. When all us fails, we can just have our tests grab ahold of a browser. Let’s look at how to truly test from the user’s perspective.
out in the world? We’re going to use a test runner called Vitest. But, it doesn’t matter. They’re all pretty much the same. It’s what I use on a daily basis and you probably want me using the tools that I’m most comfortable with for the next few hours.
anyone make you feel bad about this. Sometimes, you need to mess around and fi nd out before you settle on your fi nal approach. And, this tends to lead you writing some tests after the fact
isn’t hard. But, some code is hard to test. Your tests don’t pass because your code works. They pass because they didn’t fail. Someone is always testing your code. No one has ever broken their code into too many, small, well-named, easy-to-test functions.
some math equations some numbers does what we expect. But, what about all of the other weird stu ff that can get in there? What about unde fi ned? How about a string? Testing the unhappy path is about making sure that you’ve thought through all of the stu ff that can get weird.
mean they’re actually the same. Sure, 1 === 1 and ‘string’ === ‘string’. But, { foo: 1 } !== { foo: 1 } and [1,2,3] !== [1,2,3]. This is where toBe, toEqual, and toStrictEqual all come in.
same. toBe is useful for comparing primitive values that would = each other. toEqual looks at the contents of an object or array to see if the values are equal to each other.
two objects or arrays have the same values and structure, allowing for loosely de fi ned properties (e.g., undefined properties are not strictly compared). toStrictEqual ensures a more precise match, where even unde fi ned properties, types, and object prototypes must exactly match.
be confused with those other kinds of hooks. If you realize that you’re doing the same thing before and after every test or you want to do something before all of the tests and then clean up after all of the tests are done, then you can use hooks. But, be warned: They’re convenient, but convenience sometimes comes at the cost of clarity.
not. This use to be a bit more of a pain. But, basically, if you remember to use async and await, you should be mostly good. But, yea—you have to remember to use async and await.
Node. Node isn’t a browser. This means it doesn’t have any of the Browser APIs. The DOM is one of those APIs. This means that Node doesn’t have the DOM. This means you can’t test the DOM from Node.
this problem. Out of the box, Vitest supports two DOM libraries: JSDOM and Happy DOM. HappyDOM is small and lightweight. JSDOM is an industry standard, but it’s a heavier tool. It probably doesn’t matter which one you pick.
lunch. • It’s still not a real browser. You're not getting every subtlety of a speci fi c Chrome, Safari, or Firefox version. It's designed to act like a browser. • Running tests with jsdom can be a bit slower. It’s the cost of emulating browser stu ff . • You might still run into browser-speci fi c issues. Just because something works in Browser Mode doesn’t mean it’ll work in all browsers. (I’m looking at you, Safari.)
some complex mock logic, and now you're retracing steps, clearing call history to test cleanly. • Reset: You made a mess with return values or .mockImplementation—and now you just want to start over without rebuilding the mock. • Restore: You’re done mocking, you want to reinstate the original functionality, and walk away like nothing ever happened.
all of the information about how it was called and what it returned. This is e ff ectively the same as setting fn.mock.calls and fn.mock.results back to empty arrays. • fn.mockReset(): In addition to doing what fn.mockClear(), this method replaces the inner implementation with an empty function. • fn.mockRestore(): In addition to doing what fn.mockReset() does, it replaces the implementation with the original functions.
out the history of calls and return values on the spies, but does not reset them to their default implementation. This is e ff ectively the same as calling .mockClear() on each and every spy. • vi.resetAllMocks: Calls .mockReset() on all the spies. It will replace any mock implementations with an empty function. • vi.restoreAllMocks: Calls .mockRestore() on each and every mock. This one returns the world to it's original state.
don’t want to or need to test other people’s code. Sometimes that code has side e ff ects. It might make network requests. It might read or write to the fi le system. You don’t want to have to deal with any of that—nor should you.
things you need. This is one of those areas, where just refactoring your code can make it easier to test. If your code relies on functionality that you pass in, then it’s a lot easier to pass in things that suit your purpose. I’ve never seen breaking your code into too many small, well-named, easy-to-test functions.
have this course called Enterprise UI Development. We go deeper into some of the topics we covered today. We also cover. Running up you tests with a pre-commit hook. Running your tests in a continuous integration environment with Github Actions. Setting up a code coverage tooling.