Skip to content

RFC: Modernize Angular Testing Library with a zoneless subpackageΒ #562

@timdeschryver

Description

@timdeschryver

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 TestBed customizations can be handled within the configureTestBed callback

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, on in favor of the bindings API
  • Remove router functionality, this can be implemented on the user-side using configureTestBed
  • Remove deferBlock functionality, this can be implemented on the user-side using configureTestBed

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions