Course outline

Angular / Intermediate / Lesson 12

Component testing and accessibility

Verify observable behavior through a host, distinguish template syntax from execution, and include keyboard, naming, and focus in the feature contract.

About 5 min readContent reviewed
On this page

Lesson goals

  • Choose model, template, component, and browser checks deliberately.
  • Use an isolated TestBed fixture for interaction contracts.
  • Treat semantic and manual accessibility checks as complementary evidence.

Each test layer can observe only part of the contract

A pure model test checks calculations and transitions. A template parser checks Angular syntax, while template compilation additionally resolves bindings and dependencies. A component fixture executes Angular with a host, and a browser test observes real interaction and platform behavior. Passing one layer does not imply the others passed. The course uses isolated local checks where practical and labels framework test fragments honestly. Keep the compiler version, providers, imports, test runner, and synchronization method explicit. A fast test with an incomplete fixture can hide the exact dependency or event-wiring defect users experience.

Use the official parser without claiming semantic execution

The complete program parses two small Angular templates using the pinned compiler package. It rejects any parser errors and prints success only after both are accepted. This verifies the grammar of a binding and a control-flow block; it does not prove that count or ready exists on a component, that directives are imported, or that a browser renders the intended result. A malformed template can be caught cheaply at this layer, while a missing standalone dependency needs compilation or a component fixture. Keep the limitation in test reports instead of calling every successful parse an end-to-end test.

typescript Complete official-template-parser check
import { parseTemplate } from "@angular/compiler";
const templates = [
  `<button type="button">{{ count() }}</button>`,
  `@if (ready()) { <p>Ready</p> } @else { <p>Wait</p> }`
];
for (const template of templates) {
  const result = parseTemplate(template, "fixture.html");
  if (result.errors?.length) { throw new Error("template parse failed"); }
}
console.log("2 templates parsed");
Expected output
2 templates parsed

Both template strings parse successfully. Component members, imports, rendering, and accessibility are outside this parser-only assertion.

Exercise the user-facing event path

The contextual test assumes the SignalCounter component from the signals lesson and a configured Angular 20 test environment. It imports the standalone component, creates its fixture, renders, activates the actual button, and checks resulting text after a render check. This is stronger than calling a handler directly because it includes template wiring. It is still a framework-runner fragment, not an independently executable script in Node. Use the runner’s supported async stabilization for asynchronous state instead of arbitrary sleeps. A component’s providers and imported dependencies must match the contract being tested, with deliberate doubles for external services.

typescript Contextual TestBed interaction test
import { TestBed } from "@angular/core/testing";
import { SignalCounter } from "./signal-counter";

it("increments through the button", async () => {
  await TestBed.configureTestingModule({ imports: [SignalCounter] }).compileComponents();
  const fixture = TestBed.createComponent(SignalCounter);
  fixture.detectChanges();
  const button = fixture.nativeElement.querySelector("button") as HTMLButtonElement | null;
  if (!button) { throw new Error("missing button"); }
  button.click();
  fixture.detectChanges();
  expect(fixture.nativeElement.textContent).toContain("3 / 6");
});

In the stated runner, the assertion should pass only if the button binding updates the displayed values. This test fragment is not executed by the local parser program.

Test names, semantics, and focus rather than appearance alone

A visible control needs an understandable accessible name and appropriate native semantics. Querying by role and name can expose missing labels, while direct CSS lookup alone may miss that defect. Check keyboard activation, visible focus, error association, disabled behavior, and focus after navigation or dialog closure. Automated structural checks are useful but cannot prove the complete assistive-technology experience. Manual review should use realistic tasks and supported devices without claiming one tool certifies accessibility. A component that passes its business assertion can still be unusable by keyboard, so retain both functional and accessibility expectations.

Preserve the smallest complete failing fixture

When a test fails, retain the component, template, imports, providers, data, and exact runner command needed to reproduce it. Avoid broad schema suppressions that make unknown bindings disappear without rendering the intended feature. Test empty, loading, denied, and error states, not only a populated success. Verify teardown so subscriptions and test doubles do not leak into later cases. Do not contact real endpoints or mutate shared application configuration to satisfy a test. Report exactly which local checks ran, and keep unexecuted framework fragments labeled. The goal is evidence about the user contract, not a high test count that mirrors private implementation details.

Common errors and fixes

A parser check is presented as a component execution test.

Why it happens
Grammar acceptance was confused with resolved runtime behavior.
How to fix it
Add a correctly configured component fixture.
Prevent it next time
Name the actual verification layer in reports.

A test calls the handler but the button is not wired.

Why it happens
The template event path was bypassed.
How to fix it
Activate the rendered control in a host fixture.
Prevent it next time
Keep model and interaction tests complementary.

A mouse-only test misses an unlabeled keyboard control.

Why it happens
Visual success was treated as complete accessibility evidence.
How to fix it
Check semantic names, keyboard behavior, and focus.
Prevent it next time
Combine automated checks with manual task review.

Check your understanding

1. Remove the click binding from SignalCounter while keeping increment logic elsewhere. Which tests should fail and which may still pass?

Show worked solution

A pure transition test can still pass because the arithmetic is correct. The host interaction test should fail when clicking no longer changes the displayed values. A parser-only test may also pass because the remaining markup is valid syntax. This demonstrates why test layers protect different promises and why a single passing model test is insufficient for a component feature.

2. A save button shows only an icon and passes its click test. Add an accessibility requirement and describe appropriate evidence.

Show worked solution

Require an understandable accessible name such as Save course, native button semantics, keyboard activation, and visible focus. A role/name query can check the label, while keyboard and assistive-technology review covers interaction quality. Do not treat an aria attribute’s mere presence as proof that the name is useful or that focus behavior is correct.

Key takeaways

  • Test the observable contract at the right layer.
  • Include semantic names, keyboard use, and focus.
  • Keep fixtures isolated and claims precise.

Official references