Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2945321
01 - grammar
aaronmcadam Nov 5, 2024
9591681
01-problem: grammar
aaronmcadam Nov 5, 2024
c9fd344
01-finished: grammar
aaronmcadam Nov 5, 2024
69ac0df
02: grammar
aaronmcadam Nov 5, 2024
b484c36
02-problem: grammar
aaronmcadam Nov 5, 2024
7fce601
03-problem: grammar
aaronmcadam Nov 5, 2024
89cf81a
03: grammar
aaronmcadam Nov 5, 2024
d5facda
03-01-problem: grammar
aaronmcadam Nov 5, 2024
4b27e1b
03-solution: grammar
aaronmcadam Nov 5, 2024
25a2194
03-02-problem: grammar
aaronmcadam Nov 5, 2024
b6f9d30
03-02-solution: grammar
aaronmcadam Nov 5, 2024
806604c
03-03-problem: grammar
aaronmcadam Nov 5, 2024
396c28b
03-03-solution: grammar
aaronmcadam Nov 5, 2024
7b2eaf7
03-03-finished: grammar
aaronmcadam Nov 5, 2024
6ec5b64
04: typos and grammar
aaronmcadam Nov 5, 2024
d007029
04-01-problem: grammar
aaronmcadam Nov 5, 2024
4fd7917
04-01-solution: grammar
aaronmcadam Nov 5, 2024
6df53f4
04-02-problem: grammar
aaronmcadam Nov 5, 2024
dbaef46
04-02-solution: grammar
aaronmcadam Nov 5, 2024
d443c3a
04-03-problem: grammar
aaronmcadam Nov 5, 2024
a868e8c
04: grammar
aaronmcadam Nov 5, 2024
26e5720
05: grammar
aaronmcadam Nov 5, 2024
b6cde65
05-01-problem: grammar
aaronmcadam Nov 6, 2024
551946c
05-01-solution: grammar
aaronmcadam Nov 6, 2024
4b7707f
05-02: typos and grammar
aaronmcadam Nov 6, 2024
7975ab2
05-02-solution: grammar
aaronmcadam Nov 6, 2024
41cbf04
05-03: grammar
aaronmcadam Nov 6, 2024
01d230d
05-03-solution: grammar
aaronmcadam Nov 6, 2024
440a3c8
05-04-problem: grammar
aaronmcadam Nov 6, 2024
a56e723
05-04-solution: grammar
aaronmcadam Nov 6, 2024
c06535c
05-05-problem: grammar
aaronmcadam Nov 6, 2024
7b4ddc5
05-05-solution: grammar
aaronmcadam Nov 6, 2024
7df92ac
06: grammar
aaronmcadam Nov 6, 2024
0d2149e
06-01-problem: grammar
aaronmcadam Nov 6, 2024
a5da02d
06-01-problem: fix error
aaronmcadam Nov 6, 2024
953e58e
06-01-solution: grammar
aaronmcadam Nov 6, 2024
1a6b9e4
06-02-problem: grammar
aaronmcadam Nov 6, 2024
29b83b1
06-02-solution: grammar and flow
aaronmcadam Nov 6, 2024
20a7a1a
finished: grammar
aaronmcadam Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion exercises/01.boundaries/01.problem.boundaries/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

<EpicVideo url="https://www.epicweb.dev/workshops/mocking-techniques-in-vitest/boundaries/test-boundaries" />

I like to start with seemingly basic and straightforward exercises because they end up growing in value as you progress through the material (and beyond). The test boundary is one of such examples. In the end, the entire purpose of mocking is to draw that boundary, and you cannot wield mocking efficiently without understanding it first.
I like to start with seemingly basic and straightforward exercises because they end up growing in value as you progress through the material (and beyond). The test boundary is one such example. In the end, the entire purpose of mocking is to draw that boundary, and you cannot wield mocking efficiently without first understanding it.
4 changes: 2 additions & 2 deletions exercises/01.boundaries/FINISHED.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

Understanding test boundaries helps you think about mocking as a tool. A tool that helps you balance what matters in the test vs what doesn't. That's really what mocking of any kind is about.

Ultimately, it is _your_ decision when to draw that line. Just know that it will affect what kind of test you get in return.
Ultimately, it is _your_ decision where to draw that line. Just know that it will affect what kind of test you get in return.

In the next couple of hours, you will learn about different mocking techniques in JavaScript to help you draw that line more efficiently and with bigger confidence. Let's go!

## Related materials

Here are a few materials that can help you develop the right conception of mocking:
Here are a few materials that can help you develop the right mental model of mocking:

- [What Is a Test Boundary?](https://www.epicweb.dev/what-is-a-test-boundary)
- [How Mocking Works?](https://howmocking.works/)
Expand Down
10 changes: 5 additions & 5 deletions exercises/01.boundaries/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test('doubles what "two" returns', () => {

This creates an _integration test_ where the connection between `one()` and `two()` is important. This test will fail if `one()` behaves unexpectedly. This test will also fail if `two()` happened to do the same, despite being the test for the `one()` function.

This behavior is neither good nor bad. You are testing the code as-is, and the code has a dependency on another code. If you do nothing about that dependency, you will bring it into the test as well, which is precisely what you want for an integration test!
This behavior is neither good nor bad. You are testing the code as-is, and the code has a dependency on some other code. If you do nothing about that dependency, you will bring it into the test as well, which is precisely what you want for an integration test!

Sometimes, however, you need to test certain behaviors in isolation. Sometimes, you need to make dependencies behave a certain way to put your tested code in the right state. In those situations, you need to establish a test boundary.

Expand Down Expand Up @@ -65,7 +65,7 @@ return result * 2
// ^^^
```

Because everything else in `one()` is now _mocked_. The test will no longer fail if `two()` has a bug, only if `one()` fails to do the multiplication correctly.
Because everything else in `one()` is now _mocked_, the test will no longer fail if `two()` has a bug — it will only fail if `one()` fails to do the multiplication correctly.

> You use mocking to preserve the dependency but lift its behavior from the code to the test, making it controllable.

Expand Down Expand Up @@ -99,15 +99,15 @@ But also, perhaps the most important question: What does mocking do to my code?

## What mocking does to your code

Mocking is an essential tool for test testing. It can be your lever to transition between different testing levels, handle dependencies, model required behaviors, and focus on particular logic (not to mention various mocking applications outside of the realm of testing!). But beware: when mocking, **you are modifying your code**.
Mocking is an essential tool for testing. It can be your lever to transition between different testing levels, handle dependencies, model required behaviors, and focus on particular logic (not to mention various mocking applications outside of the realm of testing!). But beware: when mocking, **you are modifying your code**.

<callout-info class="important">
The more you alter the system under test, the more you are testing a different
system.
</callout-info>

Any mock you introduce creates a tiny alternative universe where the code you are testing is mostly the same but not quite. Those "not quite"s can be desired and controllable, but they can also decrease the quality of your tests and make you trust them less. Like any tool, mocking has to be handled with purpose and care.
Any mock you introduce creates a tiny parallel universe where the code you are testing is mostly the same but not quite. Those "not quite"s can be desired and controllable, but they can also decrease the quality of your tests and make you trust them less. Like any tool, mocking has to be handled with purpose and care.

You don't want to end up in the state where you are running tests against mocks alone. If that happens, you aren't testing anything and should take a good dozen of steps back to reconsider your testing strategy.
You don't want to end up in the state where you are running tests against mocks alone. If that happens, you aren't testing anything and should take a good step back and reconsider your testing strategy.

In this workshop, we will take a look at the most common applications of mocking and also when and how to use those. But before we begin, let's make sure you get the hang of the concept of a "test boundary" first.
2 changes: 1 addition & 1 deletion exercises/02.functions/02.problem.spies/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ constructor(private logger: Logger) {}

This means we can use a _dependency injection_ to provide it with whichever logger instance we want in test. For example, a logger that we will 🕵️ _spy on_.

_Spying_ is a technique to record a function's execution. It's like pressing a record button on a radio to listen to any function calls while it's being used. Spies give us access to the information like the number of function calls, the exact arguments provided, and values returned. Spies also allow us to completely override the behavior of any function, if we want to.
_Spying_ is a technique to record a function's execution. It's like pressing a record button on a radio to listen to any function calls while it's being used. Spies give us access to information like the number of function calls, the exact arguments provided, and the values returned. Spies also allow us to completely override the behavior of any function, if we want to.

You will use both recording and overriding in this exercise.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ export class OrderController {

That's the responsibility Peter bestowed upon himself.

Your responsibility, however, is to test the `.createOrder()` method on **the** `controller` class. There are couple of ways you can use mocking to help you with that, especially when it comes to modeling different item availability states.
Your responsibility, however, is to test the `.createOrder()` method on the `OrderController` class. There are a couple of ways you can use mocking to help you with that, especially when it comes to modeling different item availability states.

👨‍💼 Write automated tests for _both_ of the expectations toward the `.createOrder()` method by using [`.mockReturnValue()`](https://vitest.dev/api/mock.html#mockreturnvalue) and [`.mockImplementation()`](https://vitest.dev/api/mock.html#mockimplementation) utilities in Vitest to control the behavior of the `.isItemInStock()` method. By the end of the exercise, `npm test` must pass!
14 changes: 7 additions & 7 deletions exercises/02.functions/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

<EpicVideo url="https://www.epicweb.dev/workshops/mocking-techniques-in-vitest/functions/intro-to-functions" />

Functions is the heart and soul of any logic in JavaScript. Event handlers, callbacks, utilities—you have likely worked with functions before. Whenever you want your code to _do_ something you reach out to a function because it represents _action_.
Functions are the heart and soul of any logic in JavaScript. Event handlers, callbacks, utilities—you have likely worked with functions before. Whenever you want your code to _do_ something you reach out to a function because it represents _action_.

Ideally, testing functions should come down to the I/O (input/output) testing. You provide a function with the right input and assert the expected output. This testing strategy is extremely prevalent in unit testing and it truly shines with pure functions.

But the thing is, functions, much like the intentions behind them, can be different. Not all functions you will write will be pure. Some will have side effects, some will be asynchronous, the others will introduce dependencies or non-deterministic behaviors. There may also be times when the function itself wouldn't matter, and instead you would want to test if it's being called or not.
But the thing is, functions, much like the intentions behind them, can be different. Not all functions you will write will be pure. Some will have side effects, some will be asynchronous, and others will introduce dependencies or non-deterministic behaviors. There may also be times when the function itself doesn't matter, and instead you would want to test if it's being called or not.

There are plenty of cases to reach out to mocking when testing functions, and it's time you learned how to do it.
There are plenty of cases where mocking is useful when testing functions, and its time you learned how to do it.

## Mocking functions

Mocking imbues your functions with superpowers. There are multiple techniques of mocking functions, and they all come down to the following points:
Mocking imbues your functions with superpowers. There are multiple techniques for mocking functions, and they all come down to the following points:

1. Recording the function calls;
1. Replacing the function's implementation;
1. Recording function calls;
1. Replacing function implementations;
1. Creating placeholder functions to use as arguments.

In this exercise block, you will learn how to wield all three and also see the use cases for different kinds of function mocks. Let's begin!
In this exercise block, you will learn how to wield all three and explore use cases for different types of function mocks. Let's begin!
4 changes: 2 additions & 2 deletions exercises/03.date-and-time/01.problem.date-time/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function getRelativeTime(date: Date): string {

> We are also using the standard [`Intl.RelativeTimeFormat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) API to help us format the time: "1 minute ago" but "5 minute<u>s</u> ago". Give it a read if you're not familiar with it!

By using mocking, we can become magicians who can bend time and space! Well, maybe just time, for now. Vitest comes with a built-in [`vi.useFakeTimers()`](https://vitest.dev/api/vi.html#vi-usefaketimers) utility to make otherwise unpredictable date and time fixed and given.
By using mocking, we can become magicians who can bend time and space! Well, maybe just timefor now. Vitest comes with a built-in [`vi.useFakeTimers()`](https://vitest.dev/api/vi.html#vi-usefaketimers) utility to make otherwise unpredictable date and time fixed and given.

Since mocking time is such a common use case, Vitest gives you a more comfortable API to work with while also allowing to toggle between the "fake" and the "real" date whenever you need.

Expand All @@ -57,6 +57,6 @@ vi.setSystemTime(new Date('2024-06-01 00:00:00.000Z'))
// will resolve to the midnight of the 1st of June 2024.
```

But this is, of course, not enough. We don't need a specific date but _different_ days to model relative scenarios (1 minute ago, 2 months ago, etc). Loos like each test will exist in its own, separate little universe!
But this is, of course, not enough. We don't need a specific date but _different_ days to model relative scenarios (1 minute ago, 2 months ago, etc). Each test will exist in its own separate little universe!

👨‍💼 Complete the test suite for the `getRelativeTime()` function. Remember that `vi.useFakeTimers()` is still a mock and must be treated as such (cleanup?). Verify your solution by running `npm test`.
6 changes: 3 additions & 3 deletions exercises/03.date-and-time/01.solution.date-time/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

You may be noticing a pattern here. I'm trying to keep mock definitions contained and use the hooks like `beforeAll()` and `afterAll()` to make sure no mocks are left once the tests are done (and, often, even between the tests).

Testing the `getRelativeTime()` function will be no exception:
Testing the `getRelativeTime()` function is no exception:

```ts filename=get-relative-time.test.ts nonumber lines=2,6
beforeAll(() => {
Expand All @@ -16,7 +16,7 @@ afterAll(() => {
})
```

Calling `vi.useFakeTimers()` detached this test from the real flow of time, and makes it rely on the internal fake date Vitest has. Correspondingly, the `vi.useRealTimers()` utility undoes that.
Calling `vi.useFakeTimers()` detaches this test from the real flow of time, making it rely on the internal fake date Vitest has. Correspondingly, the `vi.useRealTimers()` utility undoes that.

To fix date and time in test, I will call `vi.useSystemTime()` function and provide it with the date that I want to be treated as `Date.now()`:

Expand All @@ -35,7 +35,7 @@ expect(getRelativeTime(new Date('2024-06-01 00:00:00.000Z'))).toBe('Just now')

> This produces the time delta of `0ms`, which results in the `"Just now"` string being returned from the function.

For the tests that feature an actual time difference, I have to use a slightly different setup. Firsts, the system time (which is `Date.now()`) should be set to the time that has _already passed_. Then, the `getRelativeTime()` function will accept the _starting time_ as an argument.
For the tests that feature an actual time difference, I have to use a slightly different setup. First, the system time (which is `Date.now()`) should be set to the time that has _already passed_. Then, the `getRelativeTime()` function will accept the _starting time_ as an argument.

```ts filename=get-relative-time.test.ts nonumber lines=2
test('returns "minute ago" for a date a minute ago', () => {
Expand Down
2 changes: 1 addition & 1 deletion exercises/03.date-and-time/02.problem.timers/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<EpicVideo url="https://www.epicweb.dev/workshops/mocking-techniques-in-vitest/date-and-time/timers" />

[Debounce and throttle](https://kettanaito.com/blog/debounce-vs-throttle) have to be some of my favorite utility functions, and they both happened to rely on timers to work. Let's study them in more detail and also see how we would test them.
[Debounce and throttle](https://kettanaito.com/blog/debounce-vs-throttle) have to be some of my favorite utility functions, and they both happen to rely on timers to work. Let's study them in more detail and also see how we would test them.

Take a look at the `debounce` function implementation below:

Expand Down
8 changes: 4 additions & 4 deletions exercises/03.date-and-time/02.solution.timers/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<EpicVideo url="https://www.epicweb.dev/workshops/mocking-techniques-in-vitest/date-and-time/timers/solution" />

I begin from setting up the testing hooks to mock time in this test, using the `vi.useFakeTimers()` and `vi.useRealTimers()` functions, the same way I did in the previous exercise:
I begin by setting up the testing hooks to mock time in this test, using the `vi.useFakeTimers()` and `vi.useRealTimers()` functions, the same way I did in the previous exercise:

```ts filename=debounce.test.ts nonumber lines=2,6
beforeAll(() => {
Expand Down Expand Up @@ -45,7 +45,7 @@ All of those are great tools to have in your toolbelt. In this test, I will use
vi.advanceTimersByTime(250)
```

This will immediately fastforward the test universe ahead by 250ms, allowing me to write my expectations toward the tested debounced function:
This will immediately fast-forward the test universe ahead by 250ms, allowing me to write my expectations against the tested debounced function:

```ts filename=debounce.test.ts nonumber lines=3-4
vi.advanceTimersByTime(250)
Expand All @@ -54,7 +54,7 @@ expect(fn).toHaveBeenCalledOnce()
expect(fn).toHaveBeenCalledWith('one')
```

The second test case will employ everything we've learned thus far to test the repetitive calls to the debounced function:
The second test case will use everything we've learned so far to test repeated calls to the debounced function:

```ts filename=debounce.test.ts nonumber lines=8,13
test('debounces the callback until the timeout passes since the last call', () => {
Expand All @@ -78,7 +78,7 @@ test('debounces the callback until the timeout passes since the last call', () =

> Note that you can use fake timers and `vi.advanceTimersByTime()` and `vi.advanceTimersToNextTimer()` to test intervals (`setInterval`) too!

## Difference with `sleep()`
## Difference from `sleep()`

You may be wondering about the difference between `vi.advanceTimersByTime()` and something like a `sleep()` function. Since in this case we do need to wait for a fixed period of time to pass, using `sleep()` may seem like a viable alternative. But the two behave completely differently.

Expand Down
Loading