Skip to content

Comments

docs: modernize testing services guide#67180

Open
bencodezen wants to merge 1 commit intoangular:mainfrom
bencodezen:docs/update-testing-services-guide
Open

docs: modernize testing services guide#67180
bencodezen wants to merge 1 commit intoangular:mainfrom
bencodezen:docs/update-testing-services-guide

Conversation

@bencodezen
Copy link
Contributor

@bencodezen bencodezen commented Feb 20, 2026

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

https://angular.dev/guide/testing/services

Issue Number: N/A

What is the new behavior?

Modernizes the "Testing with services" guide in preparation for an upcoming guide that will cover "Testing with dependency injection" on a broader level.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@angular-robot angular-robot bot added the area: docs Related to the documentation label Feb 20, 2026
@ngbot ngbot bot added this to the Backlog milestone Feb 20, 2026
@bencodezen bencodezen requested a review from JeanMeche February 20, 2026 17:15
@bencodezen bencodezen added action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release adev: preview labels Feb 20, 2026
@bencodezen bencodezen force-pushed the docs/update-testing-services-guide branch from ed01617 to 68ef561 Compare February 20, 2026 17:18
@bencodezen bencodezen changed the title docs: upddate testing services guide docs: modernize testing services guide Feb 20, 2026
@bencodezen bencodezen force-pushed the docs/update-testing-services-guide branch from 68ef561 to 4941678 Compare February 20, 2026 17:58

To test this service, configure a `TestBed`, which is Angular's testing utility for creating an isolated testing environment for each test. It sets up dependency injection and lets you retrieve service instances — simulating how Angular wires things together in a real application.

```ts { header: 'calculator.unit.ts' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little nit , according style guide

Suggested change
```ts { header: 'calculator.unit.ts' }
```ts { header: 'calculator.spec.ts' }

Copy link
Contributor

@yjaaidi yjaaidi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank for this, @bencodezen! This is really moving the testing docs in the right direction.

My feedback is mostly about how the docs might nudge users into writing over-specifying tests.
Also, maybe the example could use a fake instead of stub. It has the side effect of keeping this focused on Angular APIs and less about Vitest features.

Spies and stubs could appear later in a different chapter.

What do you think?

expect(value).toBe('observable value');
beforeEach(() => {
// Sets up a TestBed with no dependencies at this time
TestBed.configureTestingModule({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestBed#configureTestingModule could be introduced later as it's not required in this case.
Or maybe, the docs could just mention that this calls is not necessary if there is nothing to configure.


As a service _consumer_, you don't worry about any of this.
You don't worry about the order of constructor arguments or how they're created.
Most services depend on other services to run properly. When testing these services, controlling these dependencies enables you to ensure that the tests stay focused on the service's own logic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This comment might trigger a long conversation about "what is a unit-test" 😅
I really don't mean to be pedantic but I think that doc example can nudge the testing strategy more than one could think of.

I am afraid that this sentence controlling these dependencies enables you to ensure that the tests stay focused  nudges users to write "over-narrow" tests.
I would make it feel like an opt-in like sometimes, you might want to exclude some of these dependencies...

More about this:

In other words, replacing dependencies with test doubles does not necessarily change the focus.
The System Under Test could be the OrderTotal while the exercised code is OrderTotal + TaxCalculator. For example, Martin Fowler calls these sociable unit-tests.

Actually, replacing dependencies often harms the focus because the developer's cognitive load is over-loaded with the maintenance of the test double. Unless, when the test double is a reusable fake 😇

expect(service.getValue()).toBe('real value');
});
```
In this example, `OrderTotal` uses `inject()` to request `TaxCalculator` from Angular's dependency injection system. In production, Angular provides the real implementation. However, since your focus is testing the `OrderTotal` service, you can substitute it with a controlled replacement to isolate `OrderTotal`'s logic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my previous comment maybe this could just mention that the complexity of TaxCalculator makes us want to substitute it.
The idea is to nudge users to really think twice before using a test double, not just replace any dependencies to focus on the "thing" they are testing.

In this example, `OrderTotal` uses `inject()` to request `TaxCalculator` from Angular's dependency injection system. In production, Angular provides the real implementation. However, since your focus is testing the `OrderTotal` service, you can substitute it with a controlled replacement to isolate `OrderTotal`'s logic.

Or inside the `beforeEach()` if you prefer to inject the service as part of your setup.
### Replacing a dependency with a stub
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏🙏🙏 thank you for using the word stub and never abusing the word mock in this doc.

```ts
let masterService: MainService;
let valueServiceSpy: Mocked<ValueService>;
const taxCalculatorStub = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about typing this? Pedagogically speaking it's arguable to do it here or keep it for later but not typing the test doubles is a very common source of trouble.

// Verify the interaction with a spy
it('passes the subtotal to the tax calculator', () => {
service.total(100);
expect(taxCalculatorStub.calculate).toHaveBeenCalledWith(100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe toHaveBeenCalledExactlyOnce instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer adev: preview area: docs Related to the documentation target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants