Skip to content

bobbyg603/ng-testing-tips-ng-mocks

Repository files navigation

angular-testing-tips-ng-mocks

Angular Testing Tips: Ng-Mocks

This repo demonstrates how to use the ng-mocks and jasmine-auto-spies to write better Angular unit tests. A companion article for this repo can be found on Medium.

Steps 🥾

Clone or fork this repo

git clone https://github.com/bobbyg603/ng-testing-tips-ng-mocks

Install the dependencies

cd ng-testing-tips-ng-mocks && npm i 

Run the sample application

npm run start

Run the tests

npm test

Examples 🧑‍🏫

Using MockComponent, we can easily declare fake components with all the propert inputs and outputs and use them in our tests.

app.component.spec.ts@536ef8d

declarations: [
  AppComponent,
  MockComponent(FormComponent),
  MockComponent(CardComponent)
],

Declaring mocked providers is also easy to do with the help of MockProvider.

app.component.spec.ts@536ef8d

providers: [
  MockProvider(DogService, dogService)
]

Ditto for MockModule.

app.component.spec.ts@536ef8d

imports: [
  MockModule(FontAwesomeModule),
  MockModule(MatProgressSpinnerModule),
  MockModule(MatToolbarModule)
],

We can actually forgo all this noise by using MockBuilder.

app.component.ts

await MockBuilder(AppComponent, AppModule)
  .mock(FontAwesomeModule)
  .mock(MatProgressSpinnerModule)
  .mock(MatToolbarModule)
  .mock(CardComponent)
  .mock(DialogComponent)
  .mock(FormComponent)
  .provide({ provide: DogService, useValue: dogService });

MockRender can be used as a drop in replacement for TestBed, and is super helpful for rendering custom templates and testing directives. For more information on MockRender see the companion article.

We can use find, findAll, detectChanges, componentInstance, nativeElement, and querySelector to help us run expectations against child component inputs and outputs.

app.component.ts

describe('template', () => {
  beforeEach(() => {
    rendered.detectChanges();
    cardComponents = ngMocks.findAll(CardComponent).map(c => c.componentInstance);
    formComponent = ngMocks.find<FormComponent>('app-form').componentInstance;
  });

  it('should render title', () => {
    expect(rendered.nativeElement.querySelector('span.title')?.textContent).toMatch(app.title);
  });

  it('should pass breed to form', () => {
    expect(formComponent.breed).toBe(app.breed);
  });

  it('should pass breeds to form', () => {
    expect(formComponent.breeds).toBe(breeds);
  });

  it('should pass count to form', () => {
    expect(formComponent.count).toBe(app.count);
  });

  it('should create card for each dog', () => {
    dogs.forEach(dog => {
      expect(cardComponents.find(c => c.imgSrc === dog)).toBeTruthy();
    });
  });

  it('should show spinner when loading', () => {
    rendered.componentInstance.loading$ = of(true);
    rendered.detectChanges();
    expect(ngMocks.find(MatProgressSpinner)).toBeTruthy();
  });

  it('should not show spinner when not loading', () => {
    rendered.componentInstance.loading$ = of(false);
    rendered.detectChanges();
    expect(ngMocks.findAll(MatProgressSpinner).length).toBe(0);
  });

  it('should call onFormChange when form component raises formChange event', () => {
    const event = { breed: 'affenpinscher', count: 3 };
    const spy = spyOn(rendered.componentInstance, 'onFormChange');

    formComponent.formChange.emit(event);

    expect(spy).toHaveBeenCalledWith(event);
  });
});

If you found this repo valuable please subscribe to @bobbyg603 on Medium for more Angular tips and tricks.

Thanks for reading!