@daffodil/contact provides a testing package accessible at @daffodil/contact/state/testing. It provides a mock facade to facilitate unit testing of components that use the contact state.
The testing module provides a MockDaffContactFacade that can be used in place of the real DaffContactFacade during unit tests. This allows you to test component behavior without setting up the entire NgRx store.
A testing module that provides the MockDaffContactFacade in place of the real facade.
A mock implementation of DaffContactFacadeInterface where all observable streams are of type BehaviorSubject. This allows you to control the emitted values during tests using the next method.
Properties:
success$: BehaviorSubject<boolean> - Controls the success stateerror$: BehaviorSubject<DaffStateError[]> - Controls the error stateloading$: BehaviorSubject<boolean> - Controls the loading statedispatch(action: Action) - Spy-able method for testing action dispatchThe following example demonstrates how to unit test a component using the mock facade with the Jasmine testing framework and the jasmine-marbles library.
contact-form.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { DaffContactFacade, DaffContactSubmit } from '@daffodil/contact/state';
import { DaffStateError } from '@daffodil/core/state';
@Component({
selector: 'app-contact-form',
templateUrl: './contact-form.component.html'
})
export class ContactFormComponent implements OnInit {
loading$: Observable<boolean>;
success$: Observable<boolean>;
error$: Observable<DaffStateError[]>;
constructor(private contactFacade: DaffContactFacade) {}
ngOnInit() {
this.loading$ = this.contactFacade.loading$;
this.success$ = this.contactFacade.success$;
this.error$ = this.contactFacade.error$;
}
submitForm(email: string, message: string) {
this.contactFacade.dispatch(new DaffContactSubmit({ email, message }));
}
}
contact-form.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { hot } from 'jasmine-marbles';
import {
DaffContactFacade,
DaffContactSubmit,
DaffContactStateTestingModule,
MockDaffContactFacade
} from '@daffodil/contact/state/testing';
describe('ContactFormComponent', () => {
let mockContactFacade: MockDaffContactFacade;
let fixture: ComponentFixture<ContactFormComponent>;
let component: ContactFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ContactFormComponent],
imports: [
DaffContactStateTestingModule // this provides the MockDaffContactFacade
]
});
mockContactFacade = TestBed.inject(DaffContactFacade);
spyOn(mockContactFacade, 'dispatch');
fixture = TestBed.createComponent(ContactFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('when submitForm is called', () => {
const email = 'test@example.com';
const message = 'Test message';
beforeEach(() => {
component.submitForm(email, message);
});
it('should dispatch DaffContactSubmit action', () => {
const expected = new DaffContactSubmit({ email, message });
expect(mockContactFacade.dispatch).toHaveBeenCalledWith(expected);
});
});
describe('when the form is loading', () => {
beforeEach(() => {
// mock facade fields are BehaviorSubjects
// use the next method to mock stream values
mockContactFacade.loading$.next(true);
});
it('should set loading$ to true', () => {
const expected = hot('a', { a: true });
expect(component.loading$).toBeObservable(expected);
});
});
describe('when the form submission succeeds', () => {
beforeEach(() => {
mockContactFacade.success$.next(true);
});
it('should set success$ to true', () => {
const expected = hot('a', { a: true });
expect(component.success$).toBeObservable(expected);
});
});
describe('when the form submission fails', () => {
const errors = [
{ code: 'VALIDATION_ERROR', message: 'Email is required' }
];
beforeEach(() => {
mockContactFacade.error$.next(errors);
});
it('should set error$ to the error array', () => {
const expected = hot('a', { a: errors });
expect(component.error$).toBeObservable(expected);
});
});
});
Import the testing module: Use DaffContactStateTestingModule to automatically provide the mock facade.
Inject the mock facade: Get a reference to the mock using TestBed.inject(DaffContactFacade).
Spy on dispatch: Use spyOn(mockContactFacade, 'dispatch') to test that the correct actions are dispatched.
Control observable values: Use .next() on the BehaviorSubjects to emit test values:
mockContactFacade.loading$.next(true);
mockContactFacade.success$.next(true);
mockContactFacade.error$.next([{ code: 'ERROR', message: 'Failed' }]);
Test observables with marbles: Use jasmine-marbles to test that observables emit the expected values:
import { hot } from 'jasmine-marbles';
const expected = hot('a', { a: true });
expect(component.loading$).toBeObservable(expected);
If you prefer not to use jasmine-marbles, you can test observables with simple subscriptions:
describe('when the form is loading', () => {
it('should set loading$ to true', (done) => {
mockContactFacade.loading$.next(true);
component.loading$.subscribe(loading => {
expect(loading).toBe(true);
done();
});
});
});