Repository navigation
RFC: Modernize Angular Testing Library with a zoneless subpackage #562
Description
First of all, happy new year and my best wishes to you! 🕺💃
TLDR: I want to modernize Angular Testing Library (ATL)
History
ATL is created to test angular components with the following requisites:
- for developers: make testing easier and maintainable
- for users: provide a good experience with accessibility in mind
The more your tests resemble the way your software is used, the more confidence they can give you.
To achieve this ATL is as a wrapper around DOM Testing library, which allows you to interact with components and verify its behavior.
The main concepts for testing Angular components are the TestBed and the Angular change detection, these two are handled automatically by ATL. This means you (almost) don't have to fire change cycle manually (detectChanges()) because it's fired by the event.xyz(), findBy, and waitFor utility functions.
From the start the core API is almost unchanged, with some additions along the way to provide the functionality for new Angular API's.
The Problem
Angular has changed over the past year by providing us signals and a zoneless environment.
This also impacts the way we test components. Because it makes all(?) of the change detection cycles obsolete. I also don't want the test environment to trigger them, instead, I want a experience close to a "real production" experience.
The proposed solution
We can make this behavior configurable, but I want to take this opportunity to also remove deprecated features and less used features. Because many application rely on "older" Angular, I want to create a new subpackage where the automatic change detection will be removed and where many options and functionality will also be removed. This way the test suites for older applications or components are left untouched. In the future the new subpackage can become the default.
This means:
- No invocations of
detectChanges(), this is handled by Angular - Less custom code/behavior, use new Angular testing utilities
- Further
TestBedcustomizations can be handled within theconfigureTestBedcallback
In code this translates to the following minimal API with only the core functions:
export interface RenderResult<ComponentType> {
container: HTMLElement;
debug: (
element?: Element | Document | (Element | Document)[],
maxLength?: number,
options?: PrettyDOMOptions,
) => void;
fixture: ComponentFixture<WrapperType>;
}
export interface RenderOptions<Q extends Queries = typeof queries> {
configureTestBed?: (testbed: TestBed) => void;
bindings?: Binding[];
providers?: (Provider | EnvironmentProviders)[];
queries?: Q;
}Impact on you
As I mentioned earlier, this will not impact you in the short future.
But, if you want to test your new components that are zoneless you can make use of the new subpackage.
The end result should be a thinner layer around the Angular API.
For the new subpackage the requisites (mentioned at the top) are still answered.
// 👇 Import from subpackage
import {render, screen} from '@testing-library/angular/zoneless';
test('interacts with the component', async () => {
// 👇 We can just re-use the render method
await render(FixtureComponent);
// 👇 Inputs and outputs can be set using the bindings API
await render(FixtureComponent, {
bindings: [...]
});
const incrementControl = screen.getByRole('button', { name: '+' });
const decrementControl = screen.getByRole('button', { name: '-' });
const valueControl = screen.getByTestId('value');
// 👇 Use findBy instead of getBy so the component/signal can the value
expect(await screen.findByText('5')).toBeInTheDocument();
incrementControl.click();
incrementControl.click();
// 👇 we can just use vi.waitFor instead of ATL's waitFor
await vi.waitFor(() => expect(valueControl).toHaveTextContent('7'));
})The functionality that I want to remove:
- Remove
declarations,imports,componentProperties,componentInputs,inputs,componentOutputs,onin favor of thebindingsAPI - Remove
routerfunctionality, this can be implemented on the user-side usingconfigureTestBed - Remove
deferBlockfunctionality, this can be implemented on the user-side usingconfigureTestBed
With these changes we can still provide a good testing experience. By using the latest Angular testing utilities we remove some clutter.
Call to action
If you want to get involved feel free to add a comment (or send me a DM) with the following questions in mind:
- Is there a better way to migrate existing test suites from "old" Angular to Modern Angular?
- I want to hear from you what features should be migrated over to this new package.
Activity
the-ult commentedon Jan 5, 2026
Is there a way to leverage the Vitest Browser API more?
pageinstead ofrender.. etc? Or would that defy the purpose of ATL too much?And would that cause to many breaking changes/non-compatibility when people are not using Vitest?
timdeschryver commentedon Jan 5, 2026
We want to stay framework agnostic, so not using any testing library specific. This will prevent breaking changes when you want to use Vitest.
For the naming, it will still be
renderto stay close to the Testing Library family.shaman-apprentice commentedon Jan 15, 2026
Love the idea and thanks for reaching out and all your work!
I am not sure about the docu updates. On first thought, as a consumer, I would like the example section to show the new thin package
@testing-library/angular/zonelessand add references to more examples with the "old package".Two nitpicks:
I am not sure what the findBy can do instead of getBy?
OPINIONATED: I like explicit imports instead of the global implicit onces and guess more people would use them, if examples would use them as well 😇
timdeschryver commentedon Jan 31, 2026
@shaman-apprentice I missed your message, sorry.
I think this will be the way when we're happy with the zoneless package.
findBy tries to find the element a few times, this will be required more because it can take a tick before the signal's value is set/rendered.
I feel the same way. This is just an example though, using the built-in waitFor of ATL will also work.
shaman-apprentice commentedon Feb 1, 2026
That makes sense, thanks for the clarification! I 100% agree on the content; maybe reformulate
// 👇 Use findBy instead of getBy so the component/signal can the value->// 👇 Use findBy instead of getBy so the component/signal has time to update the valuechristophw-bird commentedon Mar 3, 2026
Why would we
waitForafterclickat all, can't we force changeDetection after every interaction or await a macrotask after every click under the hood?My preferred API would be:
timdeschryver commentedon Mar 8, 2026
Good news, that will work (see #570).
We should also await the result when a signal is updated directly.
github-actions commentedon Mar 17, 2026
🎉 This issue has been resolved in version 19.2.0 🎉
The release is available on:
Your semantic-release bot 📦🚀