Angular Testing Tools
Makes Angular testing easier
In a nutshell
This library aims to reduce boilerplate 😎 and provides high-level tools️ 🔥 for testing Component, Service, Interceptor and everything else related to the Angular mechanism.
It makes tests easier to read 😌 and faster to write ⚡️!
Quick examples
Testing Component
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent) // 🛠️ Create the test bed which is re-compiled for each test
.inject('prefs', Preferences); // 🖇️ Link a key to an injection for all tests, see below 👇
it('should render title', tb(({ component, query }) => { // 🔋 Access enhanced tools for testing components
expect(component.title).toEqual('app-v17');
const span = query.findElement('.content span');
expect(span.textContent).toContain('app-v17 app is running!');
}));
it('should update preferences on click', tb(({ action, injected: { prefs } }) => { // 🤯 Retrieve injections by autocompletion
expect(prefs.approved).toBeFalse();
action.click('#my-button');
expect(prefs.approved).toBeTrue();
}));
});
🫡 (The redundant "should create" test is even called up for you!)
Testing Service
describe('AppService', () => {
const tb = serviceTestBed(AppService, { httpTesting: true }); // 🛠️ Create the test bed and enable http testing
it('should fetch cat fact', tb(({ service, http, rx }, done) => {
const mockRes = { fact: 'string', length: 6 };
rx.remind = service.getCatFact().subscribe({ // 🧯 Use rx.remind to auto unsubscribe after the end of the test
next: (res) => {
expect(res).toEqual(mockRes);
done();
},
});
http.emitSuccessResponse({ url: service.CAT_FACT_URL, body: mockRes }); // 🎭 Fake the http response of the request that matches the url
}));
});
Installation
npm install --save-dev ngx-testing-tools
Table of contents
-
Custom test beds 🤩
-
Common tools 🔋
-
External utilities 🔧
Custom test beds
All custom test beds significantly reduce the boilerplate required to perform tests and provide enhanced tools.
ComponentTestBed
ComponentTestBed Options
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent, {
// ... options (see below)
});
});
Options :
{
imports?: Importation[] = [];
providers?: AnyProvider[] = [];
declarations?: Declaration[] = [];
schemas?: SchemaMetadata[] = [];
// Disables Angular animations with `provideNoopAnimations()`.
noopAnimations?: boolean = true;
// Run component fixture `detectChanges()` before assertion.
startDetectChanges?: boolean = true;
// Useful when you only want to test the logic of the described component.
// If enabled, no template will be rendered and no change detections will be performed.
noTemplate?: boolean = false;
// Enables `HttpTools`.
httpTesting?: boolean = false;
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
// Automatically compiles the custom test bed for each test.
autoCompile?: boolean = true;
// Automatically invokes the "should create" test.
// It checks if the provided `described` instance is truthy.
checkCreate?: boolean = true;
}
ComponentTestBed Definitions
Check common definitions :
(assertion, options?) -> jasmine.ImplementationCallback
Options :
{
// Run component fixture `detectChanges()` before assertion.
startDetectChanges?: boolean = true;
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
}
Check examples : tb(..).
Examples :
it('should do something', tb(({ component, fixture, injector, action, query }) => {
component.myInput = true;
fixture.detectChanges();
const auth = injector.get(AuthService);
const inner = query.findComponent(InnerComponent);
action.click('#my-button');
// (…) expectations
}, { startDetectChanges: false }));
it('should do something', tb(({ component }, done) => {
// (…) expectations
done();
}));
it('should do something', tb(async ({ component }) => {
// (…) async expectations
}));
ComponentTestBed Tools
ComponentTools
{
// The described component fixture.
fixture: ComponentFixture<T>;
// The described component instance.
component: T;
// Enhanced tools to query elements (see below).
query: ComponentQueryTools;
// Enhanced tools to perform action on elements (see below).
action: ComponentActionTools;
}
Check common tools :
ComponentQueryTools
findComponent(selectorOrDirective) -> T
Returns the first component instance found with the provided CSS selector or directive.
Throws an Error if not found.
/!\ Querying native element (ex: button, span, etc...) that is not an Angular component will return the hosted component instance instead.
it('should do something', tb(({ query }) => {
const inner = query.findComponent(InnerComponent);
const scanner = query.findComponent('app-scanner');
// (…) expectations
}));
findAllComponents(selectorOrDirective) -> T[]
Returns an array of all component instances found with the provided CSS selector or directive.
Throws an Error if not found.
/!\ Querying native element (ex: button, span, etc...) that is not an Angular component will return the hosted component instance instead.
it('should do something', tb(({ query }) => {
const inners = query.findAllComponents(InnerComponent);
const cards = query.findAllComponents('app-card');
// (…) expectations
}));
findElement(selectorOrDirective) -> HTMLElement
Return the first native element (that extends HTMLElement
) with by the provided CSS selector or directive.
Throws an Error if not found.
it('should do something', tb(({ query }) => {
const inner = query.findElement(InnerComponent);
const button = query.findElement<HTMLButtonElement>('#my-button');
// (…) expectations
}));
findAllElements(selectorOrDirective) -> HTMLElement[]
Returns an array of all native elements that extends HTMLElement
found by the provided CSS selector or directive.
Throws an Error if not found.
it('should do something', tb(({ query }) => {
const inners = query.findAllElements(InnerComponent);
const buttons = query.findAllElements<HTMLButtonElement>('button');
// (…) expectations
}));
findDebugElement(selectorOrDirective) -> DebugElement
Returns the first debug element found with the provided CSS selector or directive.
Throws an Error if not found.
it('should do something', tb(({ query }) => {
const innerDebug = query.findDebugElement(InnerComponent);
const buttonDebug = query.findDebugElement('#my-button');
// (…) expectations
}));
findAllDebugElements(selectorOrDirective) -> DebugElement[]
Returns an array of all debug elements found with the provided CSS selector or directive.
Throws an Error if not found.
it('should do something', tb(({ query }) => {
const innerDebugs = query.findAllDebugElements(InnerComponent);
const buttonDebugs = query.findAllDebugElements('button');
// (…) expectations
}));
ComponentActionTools
click(selectorOrDirective)
Clicks on the element found by CSS selector or directive.
Throws an Error if not found.
it('should do something on click', tb(({ action }) => {
// <button id="my-button" (click)="handleClick()">
action.click('#my-button');
// <button buttonDirective (click)="handleClick()">
action.click(MyButtonDirective);
// (…) expectations
}));
emitOutput(selectorOrDirective, name, value?)
Emits output of element found by CSS selector or directive.
Throws an Error if not found.
it('should do something when output emitted', tb(({ action }) => {
// <app-scanner (result)="barcode = $event.barcode" />
action.emitOutput(AppScannerComponent, 'result', { barcode: '123456789' } /* 👈 $event */);
// <div viewport (onViewport)="enable = $event">(…)</div>
action.emitOutput(ViewportDirective, 'onViewport', true);
// <input id="my-input" (change)="handleValue($event)" />
action.emitOutput('#my-input', 'change', 'my text');
// (…) expectations
}));
ServiceTestBed
ServiceTestBed Options
describe('AppService', () => {
const tb = serviceTestBed(ServiceComponent, {
// ... options (see below)
});
});
Options :
{
imports?: Importation[] = [];
providers?: AnyProvider[] = [];
// Enables `HttpTools`.
httpTesting?: boolean = false;
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
// Automatically compiles the custom test bed for each test.
autoCompile?: boolean = true;
// Automatically invokes the "should create" test.
// It checks if the provided `described` instance is truthy.
checkCreate?: boolean = true;
}
ServiceTestBed Definitions
Check common definitions :
(assertion, options?) -> jasmine.ImplementationCallback
Options :
{
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
}
Check examples : tb(..)
ServiceTestBed Tools
ServiceTools
{
// The described service instance.
service: T;
}
Check common tools :
PipeTestBed
PipeTestBed Options
describe('AppPipe', () => {
const tb = pipeTestBed(AppPipe, {
// ... options (see below)
});
});
Options :
{
imports?: Importation[] = [];
providers?: AnyProvider[] = [];
declarations?: Declaration[] = [];
schemas?: SchemaMetadata[] = [];
// Automatically compiles the custom test bed for each test.
autoCompile?: boolean = true;
// Automatically invokes the "should create" test.
// It checks if the provided `described` instance is truthy.
checkCreate?: boolean = true;
}
PipeTestBed Definitions
Check common definitions :
- tb.import(..)
- tb.provide(..)
- tb.declare(..)
- tb.inject(..)
- tb.provide(..)
- tb.setup(..)
- tb.compile(..).
- tb(..)
PipeTestBed Tools
PipeTools
{
// The described pipe instance.
pipe: T;
// Enhanced tools to verify transformed value by the pipe.
verify: VerifyTools;
}
Check common tools :
VerifyTools
(spec: VerifySpec)
Verifies the expected value with the provided data and parameters transformed by the pipe.
Uses expect()
under the hood.
it('should transform data', tb(({ verify }) => {
verify({ data: 'value', parameters: ['prefix-'], expected: 'prefix-value' });
}));
many(specs: VerifySpec[])
Verifies many expected values for each data and parameters transformed by the pipe.
Uses expect()
under the hood.
it('should transform data', tb(({ verify }) => {
verify.many([
{ data: 'value', parameters: ['prefix-'], expected: 'prefix-value' },
{ data: 'value', parameters: ['prefix-'], expected: 'prefix-value' },
]);
}));
InterceptorTestBed
InterceptorTestBed Options
Compatible with class (that extends HttpInterceptor
) and HttpInterceptorFn
.
describe('AuthInterceptor', () => {
const tb = interceptorTestBed(AuthInterceptor, {
// ... options (see below)
});
});
Options :
{
imports?: Importation[] = [];
providers?: AnyProvider[] = [];
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
// Automatically compiles the custom test bed for each test.
autoCompile?: boolean = true;
// Automatically invokes the "should create" test.
// It checks if the provided `described` instance is truthy.
checkCreate?: boolean = true;
}
InterceptorTestBed Definitions
Check common definitions :
(assertion, options?) -> jasmine.ImplementationCallback
Options :
{
// When enabled, the assertion will end by `HttpTestingController.verify()`.
// Works only when `httpTesting` test bed option is `true`, otherwise has no effect.
verifyHttp?: boolean = true;
}
Check examples : tb(..)
InterceptorTestBed Tools
InterceptorTools
{
// The described interceptor instance.
interceptor: T;
// Enhanced tools to inspect outgoing request and incoming response.
inspect: InpectTools;
}
Check common tools :
- BaseTools
- HttpTestingTools (the described interceptor is added to http interceptors)
InspectTools
request(..) -> Observable<HttpRequest<unknown>>
Inspect the passed request into the described interceptor.
Overload signatures :
request(req: HttpRequest)
request(method: 'GET' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'JSONP', url: string)
request(method: 'POST' | 'PUT' | 'PATCH', url: string, body: any)
Example :
it('should add "x-custom-header" to headers', tb(({ inspect, rx }, done) => {
const req = new HttpRequest('GET', '/test');
expect(req.headers.has('x-custom-header')).toBeFalse();
rx.remind = inspect.request(req).subscribe({
next: (interceptedReq) => {
expect(interceptedReq.headers.has('x-custom-header')).toBeTrue();
done();
},
});
}));
successResponse(..) -> Observable<HttpEvent<unknown>>
Inspect the passed http response into the described interceptor.
Overload signatures :
successResponse(res: HttpResponse\<unknown\>): Observable\<HttpEvent\<unknown\>\>
successResponse(url: string, body: any): Observable\<HttpEvent\<unknown\>\>
Example :
it('should do something on success response', tb(({ rx, inspect }, done) => {
const mockRes = new HttpResponse({ body: {} });
rx.remind = inspect.successResponse(mockRes).subscribe({
next: (res) => {
// (…) expectations
done();
},
});
}));
errorResponse(..) -> Observable<HttpEvent<unknown>>
Inspect the passed http error response into the described interceptor.
Overload signatures :
errorResponse(res: HttpErrorResponse): Observable\<HttpEvent\<unknown\>\>
errorResponse(url: string, error: any): Observable\<HttpEvent\<unknown\>\>
Example :
it('should do something on error response', tb(({ rx, inspect }, done) => {
const mockErr = new HttpErrorResponse({ error: 'Error' });
rx.remind = inspect.errorResponse(mockErr).subscribe({
error: (err) => {
// (…) expectations
done();
},
});
}));
ModuleTestBed
ModuleTestBed Options
describe('AppModule', () => {
const tb = moduleTestBed(AppModule, {
// ... options (see below)
});
});
Options :
{
imports?: Importation[] = [];
providers?: AnyProvider[] = [];
// Automatically compiles the custom test bed for each test.
autoCompile?: boolean = true;
// Automatically invokes the "should create" test.
// It checks if the provided `described` instance is truthy.
checkCreate?: boolean = true;
}
ModuleTestBed Definitions
Check common definitions :
ModuleTestBed Tools
ModuleTools
{
// The described module instance.
module: T;
}
Check common tools :
Common definitions
Definitions
import(oneOrManyImports) -> BaseTestBed
Imports required module(s) and standalone component(s) into testing module for your current tests.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent)
.import(SharedModule)
.import([StandaloneComponent, MaterialModule]);
});
provide(oneOrManyProviders) -> BaseTestBed
Provides required injectable service(s) or other(s) provider(s) into testing module for your current tests.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent)
.provide(AppService)
.provide([StoreService, { provide: MY_TOKEN, useValue: mockValue }]);
});
declare(oneOrManyDeclarations) -> RendererTestBed
Declares required non-standalone component(s), directive(s) and pipe(s) into testing module for your current tests.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent)
.declare(AppFirstComponent)
.declare([AppSecondComponent, AppPipe]);
});
inject(name, token) -> BaseTestBed
Links an injected instance to a key and retrieve it into the enhanced tools by autocompletion.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent)
.inject('auth', AuthService);
it('should do something', tb(({ injected: { auth } }) => {
// (…) expectations
}));
});
setup(action) -> jasmine.ImplementationCallback
Setups extra action using the enhanced tools.
Works only for beforeEach
and afterEach
.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent);
beforeEach(tb.setup(({ component }) => {
component.myInput = true;
}));
});
compile() -> Promise<void>
To be used when you need to do extra setups before compiling the custom test bed.
It has to be used into beforeEach()
setup.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent, { autoCompile: false });
beforeEach(() => {
// (…) my extra setup
return tb.compile();
});
});
(assertion, options?) -> jasmine.ImplementationCallback
Wraps the it
assertion function and provides enhanced tools for testing component expectations.
It supports the jasmine DoneFn
and async
/await
(check examples below).
Examples :
it('should do something', tb((tools) => {
// (…) expectations
}, { /* options */ }));
it('should do something', tb((tools, done) => {
// (…) expectations
done();
}));
it('should do something', tb(async (tools) => {
// (…) async expectations
}));
Common tools
BaseTools
{
// The root injector.
injector: Injector;
// Injected instances (check tb.inject(..) method).
injected: { [k: string]: any; };
// Box that automatically clears all supplied "Subscription" and "Subject".
rx: RxBox;
}
injector
it('should do something', tb(({ injector }) => {
const service = injector.get(AppService);
// (…) expectations
}));
injected
Get instance injected with tb.inject(..) by autocompletion.
describe('AppComponent', () => {
const tb = componentTestBed(AppComponent)
.inject('storage', StorageService)
.inject('auth', AuthService);
});
it('should do something', tb(({ injected: { storage, auth } }) => {
// (…) expectations
}));
rx
it('should do something', tb(({ rx }) => {
// Auto unsubscribe after the test end
rx.remind = myObservable.subscrible();
// Auto complete after the test end
const subject = new Subject();
rx.remind = subject;
// (…) expectations
}));
HttpTestingTools
{
// Only when the test bed option `httpTesting` is `true`.
http: HttpTools;
}
client
Angular HttpClient
.
controller
Angular HttpTestingController
.
emitSuccessResponse(config)
Fakes a http success response for the request that matches the url.
it('should do something', tb(({ http }, done) => {
const mockRes = 'result';
http.get('/test').subscribe({
next: (value) => {
expect(value).toEqual(mockRes);
done();
},
});
http.emitSuccessResponse({ url: '/test', body: mockRes });
}));
emitErrorResponse(config)
Fakes a http error response for the request that matches the url.
it('should do something', tb(({ http }, done) => {
http.get('/test').subscribe({
error: ({ status }) => {
expect(status).toEqual(401);
done();
},
});
http.emitErrorResponse({ url: '/test', status: 401 });
}));
External utilities
External utilities to be used inside or outside the custom test beds.
Router
Guard
challengeGuardActivate(guard, state, routeConfig?) -> R
Tests the CanActivate
guard and checks its output value.
Use the generic type to indicate the return type of the guard (
challengeGuardActivate<R>(…)
). Default isboolean
.
it('should activate', () => {
const state = TestBed.inject(Router).routerState.snapshot;
expect(challengeGuardActivate(loginGuard, state)).toBeTrue();
});
challengeGuardDeactivate(guard, component, currentState, nextState, routeConfig?) -> R
Tests the CanDeactivate
guard and checks its output value.
Use the generic type to indicate the return type of the guard (
challengeGuardDeactivate<R>(…)
). Default isboolean
.
it('should deactivate', () => {
const state = TestBed.inject(Router).routerState.snapshot;
expect(challengeGuardDeactivate(component, backGuard, currentState, nextState)).toBeTrue();
});
challengeGuardMatch(guard, route, segments) -> R
Tests the CanMatch
guard and checks its output value.
Use the generic type to indicate the return type of the guard (
challengeGuardMatch<T, R>(…)
). Default isboolean
.
it('should match', () => {
expect(challengeGuardMatch(loadGuard, { data: { isAllowed: true } }, [])).toBeTrue();
});
Resolver
checkResolver(resolver, state, routeConfig?) -> R
Checks resolver output.
Use the generic type to indicate the return type of the resolver (
checkResolver<R>(…)
). Default isObservable<boolean>
.
it('should resolve', (done) => {
const state = TestBed.inject(Router).routerState.snapshot;
checkResolver(myResolver, state, { params: { id: 1 } }).subscribe({
next: (result) => {
// (…) expectations
done();
},
});
});
Demo
Check demo .spec.ts
files.
Version compatibility
Compatible with Angular >= 15.2.x
.
What's next ? 🤩
- More custom test beds :
DirectiveTestBed
RouterTestBed
- Mocks
- Angular schematics
- Website documentation
License
MIT © Rémy Abitbol.