Skip to content

Testing Angular Pipes and Directives with Karma

Pipes and directives are usually the simplest and most rewarding Angular building blocks to test — pipes because they're often pure functions in disguise, and directives through a small, dedicated test host component. This lesson covers both patterns.

Testing a Pipe Directly

Most Angular pipes can be tested without TestBed at all — instantiate the pipe class directly and call its transform() method, exactly like testing any plain function.

import { CurrencyShortPipe } from './currency-short.pipe';

describe('CurrencyShortPipe', () => {
  let pipe: CurrencyShortPipe;

  beforeEach(() => {
    pipe = new CurrencyShortPipe();
  });

  it('formats thousands with a k suffix', () => {
    expect(pipe.transform(1500)).toBe('$1.5k');
  });

  it('returns the raw value for small numbers', () => {
    expect(pipe.transform(42)).toBe('$42');
  });
});

No TestBed is needed here at all — a pure pipe with no injected dependencies is just a plain class to instantiate directly.

Testing a Directive via a Test Host Component

@Component({
  template: `<div appHighlight [color]="color"></div>`
})
class TestHostComponent {
  color = 'yellow';
}

describe('HighlightDirective', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [HighlightDirective, TestHostComponent]
    }).compileComponents();
  });

  it('applies the background color', () => {
    const fixture = TestBed.createComponent(TestHostComponent);
    fixture.detectChanges();

    const div = fixture.nativeElement.querySelector('div');
    expect(div.style.backgroundColor).toBe('yellow');
  });
});
  • A test host component gives a directive something real to attach to, mirroring how it's used in a real template.
  • This pattern is necessary because a directive alone has no template of its own to render.
  • The same ComponentFixture/detectChanges()/DOM-querying techniques from component testing apply directly here.
  • Testing the directive's effect on real DOM (like a genuinely applied style.backgroundColor) works because Karma runs a real browser.

Pipe and Directive Testing Cheat Sheet

The core patterns for each.

Building Block Testing Pattern
Pure pipe (no dependencies) Instantiate directly with new, call transform()
Impure pipe / pipe with dependencies Use TestBed.inject() for the pipe itself
Attribute directive Attach to a small test host component's template
Structural directive Test host template using *appMyDirective syntax

Testing a Pipe with Injected Dependencies

If a pipe injects a service (for example, a locale or currency-formatting service), it needs TestBed after all, since manual instantiation with new would skip Angular's dependency injection entirely.

let pipe: CurrencyShortPipe;

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [CurrencyShortPipe]
  });
  pipe = TestBed.inject(CurrencyShortPipe);
});

Testing a Structural Directive

Structural directives (like a custom *appUnless) are tested the same way as attribute directives — via a test host — but the host template uses the asterisk syntax and typically asserts on whether an element was added to or removed from the DOM.

@Component({
  template: `<div *appUnless="hide">Content</div>`
})
class TestHostComponent {
  hide = false;
}

it('removes the element when the condition is true', () => {
  const fixture = TestBed.createComponent(TestHostComponent);
  fixture.componentInstance.hide = true;
  fixture.detectChanges();

  expect(fixture.nativeElement.querySelector('div')).toBeNull();
});

Common Mistakes

  • Reaching for TestBed for a simple, dependency-free pipe when direct instantiation is simpler and faster.
  • Forgetting a directive needs a test host component, since it has no template of its own to render.
  • Not testing both the 'applied' and 'not applied' states of a conditional directive.
  • Testing a structural directive's logic without ever checking the actual resulting DOM presence/absence.

Key Takeaways

  • Pure, dependency-free pipes can be tested with plain new instantiation, no TestBed required.
  • Pipes with injected dependencies need TestBed.inject() just like services.
  • Directives require a small test host component, since they have no template of their own.
  • Structural directive tests should assert on actual DOM presence/absence, not just internal directive state.

Pro Tip

Default to testing pipes with plain new instantiation first, and only reach for TestBed once you actually hit an injected dependency. It's a small habit that keeps a large share of your pipe tests fast and dependency-free.