ngx-testing-tools
TypeScript icon, indicating that this package has built-in type declarations

2.3.0 • Public • Published

Angular Testing Tools

Makes Angular testing easier

github ci codecov coverage npm version bundle size license

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

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 :

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 :

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 is boolean.

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 is boolean.

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 is boolean.

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 is Observable<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.

Package Sidebar

Install

npm i ngx-testing-tools

Weekly Downloads

1

Version

2.3.0

License

MIT

Unpacked Size

463 kB

Total Files

295

Last publish

Collaborators

  • remscodes