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.
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.
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.
You now understand pipe and directive testing patterns. Next, learn how to debug failing Karma tests effectively.