Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions exercises/01.setup/01.problem.vscode-extension/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Visual Studio Code extension

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-01-problem" />

Think about your testing loop right now. Most likely, it looks something like this:

1. You write some code;
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/01.solution.vscode-extension/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Visual Studio Code extension

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-01-problem/solution" />

There is a couple of things that the Vitest extension enables you to do.

## Running tests inline
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/02.problem.multiple-workspaces/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Multiple workspaces

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-02-problem" />

Vitest is an extremely versatile testing framework. You can use it for your unit tests in Node.js, for your integration tests, for component-level testing with the [Browser Mode](https://vitest.dev/guide/browser/), for benchmarking... All within the same tool. But those are still quite different tests and as such, they may require different configurations to run.

You can configure multiple test projects, or [workspaces](https://vitest.dev/guide/workspace.html#defining-a-workspace), within the same repository to help Vitest differentiate between all the layers of testing you have. This is a great way to utilize Vitest to the fullest while implementing a great test coverage.
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/02.solution.multiple-workspaces/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Multiple workspaces

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-02-problem/solution" />

## Defining workspaces

First, I will configure Vitest to have two workspaces.
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/03.problem.code-coverage/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Code coverage

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-03-problem" />

Code coverage can be a useful metric if done right. I find code coverage more of a guide to tell you about the state of your test suite than a barred threshold one must pass and never, ever lower. It can point to the areas you haven't covered with tests but you are still the one to decide the appropriate strategy to address that.

<callout-info>You can learn more about code coverage, its dangers, and where you would use it in my article called :scroll: [Making Sense of Code Coverage](https://www.epicweb.dev/making-use-of-code-coverage).</callout-info>
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/03.solution.code-coverage/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Code coverage

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-03-problem/solution" />

Since I've got `@vitest/coverage-v8` installed, I will go straight to `vitest.config.ts` and set `test.coverage` option to `true`.

```ts file=vitest.config.ts add=6-8
Expand Down
2 changes: 2 additions & 0 deletions exercises/01.setup/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Setup

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-99-outro" />

Great job! :tada: The first exercise block is done. Take a quick break and come back to continue with the test context.
2 changes: 2 additions & 0 deletions exercises/01.setup/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Setup

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/setup/01-00-introduction" />

<callout-success>Proficiency with any tool starts from the proper configuration.</callout-success>

Let's kick things off by getting your more productive with Vitest. Specifically, focusing on the following areas:
Expand Down
2 changes: 2 additions & 0 deletions exercises/02.context/01.problem.custom-fixtures/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom fixtures

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-01-problem" />

Crafting custom, domain-specific utilities is a great way to elevate your test setup and turn your tests into a _composition_ of declarative steps and assertions that follow them. As always, there are multiple ways to approach test utilities. You can just import them when you need them _or_ you can use _fixtures_ instead.

Vitest's concept of a fixture is heavily inspired by Playwright, where you can access both built-in and custom fixtures from the test context:
Expand Down
2 changes: 2 additions & 0 deletions exercises/02.context/01.solution.custom-fixtures/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom fixtures

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-01-problem/solution" />

Once I've got `@faker-js/faker` installed, I will head to a newly created `test-extend.ts` module and implement my custom fixture.

When it comes to any API design, I like approaching it _usage-first_. I create the usage experience I want and then deal with the implementation details to bring that vision to life. This custom fixture is no exception.
Expand Down
2 changes: 2 additions & 0 deletions exercises/02.context/02.problem.automatic-fixtures/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Automatic fixtures

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-02-problem" />

Your custom fixtures can introduce side effects that run before and after the fixture (i.e. after the test case is done). For example, let's take a look at this `createMockFile()` fixture:

```ts filename=test-extend.ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Automatic fixtures

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-02-problem/solution" />

## Automatic fixture initialization

First, I will fix the failing test case for when the user cannot be found. The reason it's failing is because the `createMockDatabase()` fixture isn't run for that test.
Expand Down
2 changes: 2 additions & 0 deletions exercises/02.context/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Context

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-99-outro" />

Yay! :tada: And so concludes the block dedicated to the test context and custom fixtures.

The importance of the test setup cannot be overstated. Developers often forget to pay due attention to preparing those building blocks and jump straight into test cases. But that's like rushing to cook a dish without buying and preparing all the ingredients first. That is a recipe for disaster.
Expand Down
2 changes: 2 additions & 0 deletions exercises/02.context/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Context

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/context/02-00-introduction" />

A properly orchestrated test feels like putting LEGO pieces together:

```ts highlight=2-4
Expand Down
2 changes: 2 additions & 0 deletions exercises/03.assertions/01.problem.custom-matchers/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom matchers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-01-problem" />

Imagine you're building a full-stack application. Inevitably, it fetches and processes data from the server in order to render the corresponding UI. To keep it both type- and runtime-safe, you introduce a validation library to define schemas for your data types and keep them in check.

For example, you may have a user schema:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom matchers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-01-problem/solution" />

## Declaring a matcher

I will start by implementing the custom `.toMatchSchema()` matcher. This will include both extending the type definitions for `vitest` and defining the matcher logic itself.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Asymmetric matchers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-02-problem" />

_Assymetric matcher_ is the one where the `actual` value is literal while the `expected` value is an _expression_.

```ts nonumber
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Asymmetric matchers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-02-problem/solution" />

To expose my `.toMatchSchema()` matcher as an asymmetric matcher, I will extend the `AsymmetricMatchersContaining` interface of the `vitest` module:

```ts nonumber add=4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom equality testers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-03-problem" />

Assertions in tests often come down to _value comparison_. You expect the result of <span style={{ whiteSpace: 'nowrap' }}>`sum(2, 3)`</span> to equal `5`; you expect `cart` to have a property `items` of type `Array`, and so on. And, sometimes, comparing things isn't as straightforward.

For example, imagine testing software that deals with measurements. Each measurement has a `value` (e.g. 1, 5, or 57) and a `unit` (inches, millimeters, centimeters, etc). It's quite easy to compare two measurements if both their values and units are the same:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Custom equality testers

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-03-problem/solution" />

Much like custom matchers, custom equality testers live in the setup file, which in our case is `vitest.setup.ts`. To extend the built-in equality logic (the one used by `.toEqual()`), I will call the `expect.addEqualityTesters()` function and provide it with a list of custom equality testers.

```ts filename=vitest.setup.ts add=3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Retryable assertions

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-04-problem" />

When testing asynchronous code, your system often arrives at the expected state _eventually_. That is why you often express your intentions in tests as "wait for X to happen" instead of claiming "X must happen immediately".

In Promise-based APIs, that expectation is neatly abstracted behind the `async`/`await` keywords to keep your tests clean:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Retryable assertions

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-04-problem/solution" />

I will start from removing the `vi.waitFor()` block I have in the test:

```ts filename=src/client.test.ts remove=8-10
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Soft assertions

The software you build tends to get complex, which is a direct reflection of the complexity behind its requirements. Sometimes, you have multiple expectations toward your system even within a single test case (which, by the way, I find to be absolutely normal).
<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-05-problem" />

The software you build tends to get complex, which is a direct reflection of the
complexity behind its requirements. Sometimes, you have multiple expectations
toward your system even within a single test case (which, by the way, I find to
be absolutely normal).

For example, let's say you are testing a user subscription service. There, one of the expectations is that when the user cancels their subscription, their `user.subscription` object must transition to the correct state:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Soft assertions

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-05-problem/solution" />

A _soft assertion_ is a type of assertion that doesn't short-circuit your test case when it fails. Instead, all failed soft assertions are reported at the end of the test run at once.

> 🦉 The concept of soft assertions isn't new, and you can find it in other programming languages. Somehow, it's not as widespread in JavaScript, which is a shame we will try collectively to correct.
Expand Down
2 changes: 2 additions & 0 deletions exercises/03.assertions/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Assertions

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-99-outro" />

Another exercise block covered!

I hope I was successful in infecting you with my love toward assertions. You can use assertions to a great effect to elevate your test experience and make tests more useful.
Expand Down
2 changes: 2 additions & 0 deletions exercises/03.assertions/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Assertions

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/assertions/03-00-introduction" />

Assertion is the most important part of any test. Everything you do, all the setup you prepare and actions you carry out exist _only_ so you could write those assertions—your expectations toward the tested system.

And sometimes those expectations can get quite complex! There's so much your code can do. It can bring domain-specific requirements, transition between states asynchronously, or even have multiple criteria to reflect a single expectation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Profiling slow tests

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-01-problem" />

There are numerous factors that can contribute to the performance of your test suite: the number of tests, the amount of test setup, the speed of the tested code itself. But no matter the factors, you have to know _how_ to spot a slow test and _what_ to do to improve it.

When it comes to dissecting any performance degradation, a gut feeling or a guess what might be making your software slow simply won't cut it. You need to know for a fact how that degradation manifests and where it stems from. Only then can you design an appropriate plan to fix it for good.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Profiling slow tests

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-01-problem/solution" />

With the `vitest-profiler` plugin added to my `vitest.config.ts`, I can run the tests in the profiler mode to see what takes them so long.

```sh nonumber
Expand Down
2 changes: 2 additions & 0 deletions exercises/04.performance/02.problem.concurrency/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Concurrency

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-02-problem" />

For the remainder of this exercise block, I would like for us to focus on techniques to apply to performance issues caused by a _large number of tests_. Even if every single one of your test cases is blazingly-fast, if you have a million of them, your test run will still be slow.

One of the ways to fix that is by utilizing _concurrency_.
Expand Down
2 changes: 2 additions & 0 deletions exercises/04.performance/02.solution.concurrency/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Concurrency

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-02-problem/solution" />

Vitest runs each test case in a test file _sequentially_. We can speed up our test time by running tests _concurrently_ instead.

```diff filename=your.test.ts remove=1 add=2
Expand Down
9 changes: 6 additions & 3 deletions exercises/04.performance/03.problem.test-isolation/README.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Test isolation

Vitest runs every test file in a separate worker, which enables parallel execution and speeds up tests by default.
<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-03-problem" />

Vitest runs every test file in a separate worker, which enables parallel execution and speeds up tests by default.

Each worker has its own isolated environment, meaning that if one test file mutates a global variable, other test files won't be affected by this mutation. This test file-level isolation is a great feature for preventing test interference.

### The Problem
While test isolation provides benefits, **it comes with a performance cost.**

While test isolation provides benefits, **it comes with a performance cost.**

When you have hundreds or thousands of test files, the overhead of spawning a worker for each file accumulates and can significantly degrade performance.

### Your Task

Follow the instructions to disable test isolation and observe the performance difference it makes in projects with a large number of test files.
Follow the instructions to disable test isolation and observe the performance difference it makes in projects with a large number of test files.
18 changes: 7 additions & 11 deletions exercises/04.performance/03.solution.test-isolation/README.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Test isolation

## TODO

- You can spot the cost of spawning the worker as a part of the `prepare` metric in the test summary.

---
<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-03-problem/solution" />

By default, Vitest runs each test file in an isolated environment (a worker, a forked process, or a VM context). That is a sensible default to make sure that no state gets leaked between test files and also make it a bit easier for you to orchestrate test setup as you don't have to worry about the entire test suite at once, just a single test file at a time.

Expand Down Expand Up @@ -43,13 +39,13 @@ To disable test isolation and prevent Vitest from spawning a worker for each tes

```javascript
export default defineConfig({
test: {
globals: true,
isolate: false,
},
test: {
globals: true,
isolate: false,
},
})
```

Then run `npm test`.
Then run `npm test`.

What change did this make to your test run?
What change did this make to your test run?
2 changes: 2 additions & 0 deletions exercises/04.performance/04.problem.sharding/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Sharding

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-04-problem" />

Our test files are running in parallel and our test cases are running concurrently, but we can do more with Vitest. Large test suites can still be slow even despite all of that parallelism simply because there are a lot of tests to run.

This is where it comes in handy to split our entire test suite into groups (or shards) using a technique called _sharding_.
Expand Down
2 changes: 2 additions & 0 deletions exercises/04.performance/04.solution.sharding/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Sharding

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-04-problem/solution" />

To enable sharding, we need to change the way we run Vitest:

1. Provide the [`--shard`](https://main.vitest.dev/guide/cli#shard) option to the Vitest CLI, describing which shard group this process should run. For example, to run the first quarter of the tests, use `--shard=1/4`; the second quarter will be `--shard=2/4`, and so on.
Expand Down
2 changes: 2 additions & 0 deletions exercises/04.performance/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Performance

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-99-outro" />

Great job! :tada: The final exercise block is now behind you.

Now you are equipped with the tools and techniques to approach any performance degradation in your tests. You know how to profile tests and how to act based on that. You have a better understanding of how Vitest runs your tests and what defaults it has, as well as how opting out from those defaults might yield a huge performance improvement!
Expand Down
2 changes: 2 additions & 0 deletions exercises/04.performance/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Performance

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/performance/04-00-introduction" />

The second most common complain about automated tests (after them being flaky, of course) is that they are _slow_. And while flakiness often has a more unpredictable nature, performance issues with tests are more similar to a snowball. They might be there from the start but you won't notice them while having ten tests. They will become painfully apparent once you have a hundred.

**I don't want you to be punished for improving the test coverage of your software**. Neither do I want you to be left in the dark when you stare at a 10-minute long test run on CI and begin to question the rationale of your life's choices.
Expand Down
2 changes: 2 additions & 0 deletions exercises/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Advanced Vitest Patterns

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/advanced-vitest-patterns-workshop-outro/99-outro" />

Hooray! You've completed the Advanced Vitest Patterns workshop 👏👏

I hope you enjoyed the exercises and learned a great deal about Vitest and how to customize it to create great test experiences. Whether it's tackling complexity through custom fixtures and assertions, expressing your expectations more efficiently with soft and retryable assertions, or diving head-on into profiling slow tests and tweaking your test runner's defaults.
Expand Down
2 changes: 2 additions & 0 deletions exercises/README.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Advanced Vitest Patterns

<EpicVideo url="https://www.epicweb.dev/workshops/advanced-vitest-patterns/advanced-vitest-patterns-workshop-introduction/advanced-vitest-patterns-introduction" />

Hey! :wave:

Do you know this feeling when a tool “clicks”? When you suddenly become so comfortable with it that it’s no longer just a means to an end but an instrument to craft better experiences for yourself. Over the past decade, that transition has happened with a bunch of tools for me.
Expand Down
Loading