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
7 changes: 7 additions & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"projects": {
"default": "marmicode-cookbook"
},
"targets": {
"marmicode-cookbook": {
"hosting": {
"cookbook": ["marmicode-cookbook"]
}
}
}
}
7 changes: 3 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,22 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4

- run: bunx nx-cloud start-ci-run --distribute-on="3 linux-large-js-bun"
- run: bunx nx-cloud record -- nx format:check
- run: bunx nx affected -t build lint test e2e
- name: 🔐 Set up service account
if: github.ref == 'refs/heads/main'
env:
FIREBASE_SERVICE_ACCOUNT_MARMICODE_COOKBOOK: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_MARMICODE_COOKBOOK }}
run: |
echo $FIREBASE_SERVICE_ACCOUNT_MARMICODE_COOKBOOK > ~/.firebase-sa.json
echo "GOOGLE_APPLICATION_CREDENTIALS=$HOME/.firebase-sa.json" >> "$GITHUB_ENV"
- name: 🚀 Deploy
if: github.ref == 'refs/heads/main'
run: bunx nx affected -t deploy --no-agents

- name: 🔍 Smoke test
run: NX_CLOUD_DISTRIBUTED_EXECUTION=false bunx nx run-many -t smoke
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ yarn-error.log*
.nx/cache
.nx/workspace-data
.firebase
vite.config.*.timestamp*
vite.config.*.timestamp*
vitest.config.*.timestamp*
25 changes: 25 additions & 0 deletions apps/cookbook/docs/angular-testing/00-introduction/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: Introduction
slug: /angular/testing
---

# Angular Testing Cookbook

import { useDocsSidebar } from '@docusaurus/plugin-content-docs/client';
import DocCardList from '@theme/DocCardList';
import { ExternalLink } from '@site/src/components/external-link';

This cookbook is the complementary resource to the <ExternalLink href="https://courses.marmicode.io/courses/pragmatic-angular-testing" medium="in-article" content="angular-testing-intro">Pragmatic Angular Testing</ExternalLink> course.

It's for Angular developers who want to build a **pragmatic testing strategy** — **in opposition to dogmatic approaches** such as:

- _"your unit-test is not a unit-test"_
- _"let's mock everything"_
- _"let's go full E2E"_
- _"let's target 100% coverage"_

You'll learn how to write tests that are fast, readable, and resilient. You'll escape the cycle of bloated mocks and brittle end-to-end tests that slow down development.

Many teams only realize the cost of skipping these practices after months or years of pain. Lucky you! You found this resource, so you won't have to.

<DocCardList items={useDocsSidebar().items.slice(1)}/>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Beyond Unit vs. Integration
slug: /angular/beyond-unit-vs-integration
slug: /angular/testing/beyond-unit-vs-integration
toc_max_heading_level: 4
---

Expand Down Expand Up @@ -156,24 +156,24 @@ They are **either**:

Given Wide test properties, one might think that they should simply be avoided. However, Wide tests have their own advantages:

- They are more [symmetric to production](../../02-glossary.md#symmetric-to-production), and [predictive](../../02-glossary.md#predictive), thus more reassuring.
- They are more [structure-insensitive](../../02-glossary.md#structure-insensitive) _(e.g. you can refactor your code without breaking them)_.
- They are more [symmetric to production](../06-glossary.md#symmetric-to-production), and [predictive](../06-glossary.md#predictive), thus more reassuring.
- They are more [structure-insensitive](../06-glossary.md#structure-insensitive) _(e.g. you can refactor your code without breaking them)_.

## ⚖️ Comparing Narrow and Wide Tests {#comparing-narrow-and-wide-tests}

| Property | Narrow Tests | Wide Tests |
| ----------------------------------------------------------------------- | --------------------- | --------------------- |
| **[Speed](#fast)** | ⚡️ Fast | 🐢 Slow |
| **[Isolation](#easy-to-isolate-and-parallelize)** | 🏝️ Easy to isolate | 🔗 Harder to isolate |
| **[Cognitive Load](#low-cognitive-load)** | 😌 Low cognitive load | 🤯 Harder to diagnose |
| **[Precision](../../02-glossary.md#precise-tests)** | Higher Precision | Lower Precision |
| **[Production-Symmetry](../../02-glossary.md#symmetric-to-production)** | Lower Symmetry | Higher Symmetry |
| **[Structure-Sensitivity](../../02-glossary.md#structure-insensitive)** | Higher Sensitivity | Lower Sensitivity |
| Property | Narrow Tests | Wide Tests |
| -------------------------------------------------------------------- | --------------------- | --------------------- |
| **[Speed](#fast)** | ⚡️ Fast | 🐢 Slow |
| **[Isolation](#easy-to-isolate-and-parallelize)** | 🏝️ Easy to isolate | 🔗 Harder to isolate |
| **[Cognitive Load](#low-cognitive-load)** | 😌 Low cognitive load | 🤯 Harder to diagnose |
| **[Precision](../06-glossary.md#precise-tests)** | Higher Precision | Lower Precision |
| **[Production-Symmetry](../06-glossary.md#symmetric-to-production)** | Lower Symmetry | Higher Symmetry |
| **[Structure-Sensitivity](../06-glossary.md#structure-insensitive)** | Higher Sensitivity | Lower Sensitivity |

:::tip Narrow Tests Foster Good Design
It is easier to write Narrow tests for well-designed code with clear separation of concerns, and the right abstractions where we can easily replace dependencies with test doubles. Therefore **Narrow tests are more likely to foster good design**.

On the other hand, Over-Narrow tests can lead to [over-specification](../../02-glossary.md#over-specification) and make tests more [structure-sensitive](../../02-glossary.md#structure-insensitive) and brittle.
On the other hand, Over-Narrow tests can lead to [over-specification](../06-glossary.md#over-specification) and make tests more [structure-sensitive](../06-glossary.md#structure-insensitive) and brittle.
:::

## Which one to choose?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Designing a Testing Strategy
slug: /angular/pragmatic-testing-strategy
slug: /angular/testing/pragmatic-testing-strategy
toc_max_heading_level: 4
---

Expand All @@ -19,7 +19,7 @@ The main goals to keep in mind when designing a testing strategy are:

We want to prevent bugs from reaching users, just like a trapeze artist wants to avoid falling to the ground.

Tests _([Narrow](../../02-glossary.md#narrow-tests) and [Wide](../../02-glossary.md#wide-tests))_ are safety nets among others like [Dogfooding](../../02-glossary.md#dogfooding) or [Canary Release](../../02-glossary.md#canary-release).
Tests _([Narrow](../06-glossary.md#narrow-tests) and [Wide](../06-glossary.md#wide-tests))_ are safety nets among others like [Dogfooding](../06-glossary.md#dogfooding) or [Canary Release](../06-glossary.md#canary-release).

<ImageContainer size="small">![Safety Nets](./safety-nets.png)</ImageContainer>

Expand All @@ -38,7 +38,7 @@ Needless to say, developing without tests is like performing trapeze without a s

### Beware the Ice Cream Cone Model

A common oversight when designing a testing strategy is to mainly focus on [Wide tests](../../02-glossary.md#wide-tests) in general. Things get even worse when the model is topped with a few calories of manual testing.
A common oversight when designing a testing strategy is to mainly focus on [Wide tests](../06-glossary.md#wide-tests) in general. Things get even worse when the model is topped with a few calories of manual testing.

<ImageContainer size="medium">
![Ice Cream Cone Model](./ice-cream-cone-model.png)
Expand All @@ -49,7 +49,7 @@ While such a model could work for a simple product with small ambitions, it **do
The main issues with the Ice Cream Cone Model are:

- Obviously, manual testing is slow, expensive, and error-prone. _(Humans are bad at repetitive tasks.)_
- **🐢 Slow Feedback Loop**: Wide tests are slow, and we often have to run them all as it is not always easy to only run [Affected Tests](../../02-glossary.md#affected-tests) for Wide tests.
- **🐢 Slow Feedback Loop**: Wide tests are slow, and we often have to run them all as it is not always easy to only run [Affected Tests](../06-glossary.md#affected-tests) for Wide tests.
- **💰 Expensive to Maintain**: Wide tests are attractive as they might sometimes require less setup _(e.g., fewer Test Doubles)_ than Narrow tests. While this can make the initial implementation cheaper, the maintenance cost is generally much more expensive. For instance, Wide tests are not precise by nature and require a higher [cognitive load](../01-beyond-unit-vs-integration/index.mdx#low-cognitive-load). Debugging failing Wide tests is therefore a recurring and expensive task.

### What Went Wrong with the Test Pyramid?
Expand All @@ -67,10 +67,10 @@ The Test Pyramid is a great model to follow, but **it is often misunderstood for

Due to this frequent misunderstanding, **developers might end up with tests that are too Narrow**. The problem with such tests is that they are:

- **[Over-Specifying](../../02-glossary.md#over-specification)**: They are too structure-sensitive and coupled to implementation details. They do not survive refactoring.
- **Expensive to Implement**: They require substantial test setup and [test double](../../02-glossary.md#test-doubles) orchestration.
- **[Over-Specifying](../06-glossary.md#over-specification)**: They are too structure-sensitive and coupled to implementation details. They do not survive refactoring.
- **Expensive to Implement**: They require substantial test setup and [test double](../06-glossary.md#test-doubles) orchestration.
- **Expensive to Maintain**: Being over-specifying makes them expensive to maintain.
- **Not [Predictive](../../02-glossary.md#predictive)**: They are so [asymmetric to production](../../02-glossary.md#symmetric-to-production) that they are not predictive of the system's behavior. All the tests pass, but the feature is actually broken.
- **Not [Predictive](../06-glossary.md#predictive)**: They are so [asymmetric to production](../06-glossary.md#symmetric-to-production) that they are not predictive of the system's behavior. All the tests pass, but the feature is actually broken.

### The Honeycomb Testing Model

Expand Down Expand Up @@ -143,7 +143,7 @@ That is when you should consider zooming in even more with narrower tests.

## Composability

Interestingly, in the example above, we can observe that with such an approach, we can reach a relatively high level of confidence with only a few tests (<span style={{color: 'orange'}}>**1 Wide**</span>, <span style={{color: '#1298fb'}}>**2 Narrow**</span>, <span style={{color: 'green'}}>**1 narrower**</span>) and one Test Double. Our tests are [**composable**](../../02-glossary.md#composable).
Interestingly, in the example above, we can observe that with such an approach, we can reach a relatively high level of confidence with only a few tests (<span style={{color: 'orange'}}>**1 Wide**</span>, <span style={{color: '#1298fb'}}>**2 Narrow**</span>, <span style={{color: 'green'}}>**1 narrower**</span>) and one Test Double. Our tests are [**composable**](../06-glossary.md#composable).

The **two extremes that wouldn't work** are the following:

Expand All @@ -170,11 +170,11 @@ Here are some examples:
- Is it cheaper to test the integration with a Payment API or break the checkout process for a few users... or all users... then spend a few hours debugging and fixing?
- Is it cheaper to test that some rare error case is displaying an actionable error message or to have a customer support agent spend 30 minutes on the phone with each impacted user?

While you might think that the answer to all these examples is obvious, it is actually subjective. The answer depends on the context of your product, the phase of the product _(Cf. [3X](../../02-glossary.md#3x))_, the team, and the users _(their nature and volume)_, among other things.
While you might think that the answer to all these examples is obvious, it is actually subjective. The answer depends on the context of your product, the phase of the product _(Cf. [3X](../06-glossary.md#3x))_, the team, and the users _(their nature and volume)_, among other things.

### The Testing Strategy Should Be Part of the Design Doc

When designing a new feature, the testing strategy for that specific feature should be part of the [Design Doc](../../02-glossary.md#design-doc) _(or whatever drawings or notes you make before coding)_. The testing strategy should be as important as the architecture.
When designing a new feature, the testing strategy for that specific feature should be part of the [Design Doc](../06-glossary.md#design-doc) _(or whatever drawings or notes you make before coding)_. The testing strategy should be as important as the architecture.

What if there is no Design Doc? Well, maybe you should give it a try.

Expand Down Expand Up @@ -224,9 +224,9 @@ Testing, and more precisely TDD, **keeps us focused on the task at hand, enablin

### The Wedding Registry Anecdote

I had the privilege of being "born" into [eXtreme Programming](../../02-glossary.md#extreme-programming) _(XP)_. My first job _(back in 2007)_ was with a team deeply committed to XP values, principles, and practices. As the least experienced developer on the team, [Test-Driven Development](../../02-glossary.md#tdd) _(TDD)_ saved me _(among other practices)_.
I had the privilege of being "born" into [eXtreme Programming](../06-glossary.md#extreme-programming) _(XP)_. My first job _(back in 2007)_ was with a team deeply committed to XP values, principles, and practices. As the least experienced developer on the team, [Test-Driven Development](../06-glossary.md#tdd) _(TDD)_ saved me _(among other practices)_.

Thirteen years later, having always relied on testing and TDD, **I had to challenge my well-established assumptions**. What if we didn't need as much testing? Could there be situations where testing wasn't necessary at all? We don't need tests for a one-shot simple script or some [Spikes](../../02-glossary.md#spike), but we need tests for ambitious products, right? But where is the line?
Thirteen years later, having always relied on testing and TDD, **I had to challenge my well-established assumptions**. What if we didn't need as much testing? Could there be situations where testing wasn't necessary at all? We don't need tests for a one-shot simple script or some [Spikes](../06-glossary.md#spike), but we need tests for ambitious products, right? But where is the line?

**I had to try driving without a seat belt**. After all, is a seat belt really necessary in my garage? What about in a parking lot?

Expand Down Expand Up @@ -261,8 +261,8 @@ Here are some metrics that can help you evaluate your testing strategy:
- **🐞 Number of Regressions**
- **😰 Team Confidence**
- **♻️ Feedback Loop Speed**
- **🚨 Number of [False Positives](../../02-glossary.md#false-positive)**
- **🥷 Number of [False Negatives](../../02-glossary.md#false-negative)**
- **🚨 Number of [False Positives](../06-glossary.md#false-positive)**
- **🥷 Number of [False Negatives](../06-glossary.md#false-negative)**

### What About Code Coverage?

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Why Vitest?
slug: /angular/why-vitest
slug: /angular/testing/why-vitest
---

There are many test runners and testing frameworks available in the web ecosystem, but let's focus on those that are most relevant to Angular developers.
Expand Down Expand Up @@ -203,7 +203,7 @@ Vitest provides a [UI mode](https://vitest.dev/guide/ui.html) to visualize your

What's even more compelling is that Vitest's UI provides a [module graph](https://vitest.dev/guide/graph.html) to visualize the dependencies between the modules loaded by your tests.

![Vitest Module Graph](./vitest-module-graph.png)
![Vitest Module Graph](vitest-module-graph.png)

### Test Duration Report

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Migrating to Vitest
slug: /angular/migrating-to-vitest
slug: /angular/testing/migrating-to-vitest
toc_max_heading_level: 4
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
label: Vitest
link:
slug: /angular/vitest
slug: /angular/testing/vitest
type: generated-index
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Fake It Till You Mock It
slug: /angular/fake-it-till-you-mock-it
slug: /angular/testing/fake-it-till-you-mock-it
---

import { ImageContainer } from '@site/src/components/image-container';
Expand All @@ -18,7 +18,7 @@ In most cases, you can't rely on end-to-end tests alone.

Eventually, you'll need to stop code execution from flowing through the entire app — including code you own, code you don't, the network, the database, the file system, the LLMs, and so on. **You'll want to narrow down your tests** to speed things up, tighten the feedback loop, gain precision, reduce flakiness, and improve overall stability.

But **isolating code isn't as easy as just cutting connections**. You can't sever a service and call it a day. Instead, you swap out dependencies with [test doubles](../../02-glossary.md#test-doubles) _(e.g., mocks, stubs, spies, fakes, etc.)_.
But **isolating code isn't as easy as just cutting connections**. You can't sever a service and call it a day. Instead, you swap out dependencies with [test doubles](../06-glossary.md#test-doubles) _(e.g., mocks, stubs, spies, fakes, etc.)_.

<ImageContainer size="medium">
![Test Doubles](./test-doubles.png)
Expand All @@ -32,7 +32,7 @@ It is common to refer to all test doubles as "mocks," but technically, mocks are

## The Mock

A mock is a test double that's pre-programmed with interaction expectations — before the [System Under Test (SUT)](../../02-glossary.md#system-under-test-sut) is even exercised, namely during the "arrange" phase of the test.
A mock is a test double that's pre-programmed with interaction expectations — before the [System Under Test (SUT)](../06-glossary.md#system-under-test-sut) is even exercised, namely during the "arrange" phase of the test.

> _Tests written using *Mock Objects* look different from more traditional tests because all the expected behavior must be specified **before** [emphasis in original] the SUT is exercised._
>
Expand Down Expand Up @@ -79,7 +79,7 @@ You have to program the methods through the `repo` object to ensure they are typ
:::

:::warning
One major drawback of mocks is that a failed assertion will throw an error, which is caught by the test framework _(e.g., Vitest)_. However, if the SUT catches the error, the test will pass and result in a [false negative](../../02-glossary.md#false-negative).
One major drawback of mocks is that a failed assertion will throw an error, which is caught by the test framework _(e.g., Vitest)_. However, if the SUT catches the error, the test will pass and result in a [false negative](../06-glossary.md#false-negative).
:::

## The Spying Stub
Expand Down
12 changes: 12 additions & 0 deletions apps/cookbook/docs/angular-testing/05-prep-station/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Migration Prep Station
slug: /angular/testing/prep-station
---

Welcome to the Prep Station — your "mise en place" before Angular updates, and migrations.

This section helps you get ready for Angular version bumps and other breaking changes. Start here before things catch fire.

import DocCardList from '@theme/DocCardList';

<DocCardList />
Loading