question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to Unit Test a Component that extends a base Class

See original GitHub issue

I’m unable to get my Jasmine tests to work on my Component that extends a base class. I have tried using a mock for the base class, but it seems to be ignored.

Here’s the SupplierComponent (simplified) that I need to test. See how it extends the BaseEditComponent

import { Component } from '@angular/core';
import { BaseEditComponent } from '../base-edit.component';

@Component({
  selector: 'app-supplier',
  templateUrl: './supplier.component.html',
  styleUrls: ['./supplier.component.scss'],
})
export class SupplierComponent extends BaseEditComponent {
  constructor() {
    super();
  }
}

BaseEditComponent is defined as just an abstract class (not a Component). It has a bunch of extra dependencies that are out of scope for testing the SupplierComponent. A similar inheritance setup is discussed in this blog entry.

Here’s how I have configured my TestBed. See how I have told it to useClass MockBaseEditComponent instead of BaseEditComponent in the providers.

import { } from 'jasmine';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SupplierComponent } from './supplier.component';
import { BaseEditComponent } from '../base-edit.component';

export class MockBaseEditComponent {
}

describe('component: SupplierComponent', () => {

  let component: SupplierComponent;
  let fixture: ComponentFixture<SupplierComponent>;
  let editService: ProgramEditService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [SupplierComponent],
      providers: [
        { provide: BaseEditComponent, useClass: MockBaseEditComponent },
      ],
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(SupplierComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeDefined();
  });    
  
});

When I run, I get an exception because Jasmine has loaded the real BaseEditComponent which has an unfulfilled dependency, instead of loading my empty MockBaseEditComponent.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:12
  • Comments:8

github_iconTop GitHub Comments

18reactions
FirstVertexcommented, Aug 16, 2018

Angular does not provide a way to replace the base class with a stub. However, I have discovered that isn’t really necessary.

We can create a Jasmine Spy for each of the methods in the base class, and verify they have been called. That relieves us from testing all of the base class implementation when we just want to focus on the sub class.

spyOn(component, methodInBase).and.callThrough()

If there are injected dependencies to the sub class, or the super class, we do have the chance to replace these with Mocks using the TestBed.configureTestingModule. If our sub class has its own constructor, the Mocks will be injected there and passed to the super class with the call to super() found in the subclass’s constructor. If our sub class doesn’t have its own constructor, Angular will call the base class’s constructor directly, with the injected Mock services we have set up.

Therefore, no special setup or config is needed to unit test this situation. We just need to make sure to mock all the dependencies and they will be injected as expected, whether it’s in the base or the sub.

For a large project with lots of classes that extend a base class, I have found that it is useful to create Mocks for each of my injected services. For a service named supplier.service.ts beside that in the same folder I am now in the habit of making a supplier.service.mock.ts which has the same interface as the real service but has only Jasmine Spys for all of the method implementations. Then, each time I need to test a component that extends a base, I just inject all the mocks that are needed by the base with the TestBed.configureTestingModule. There is some boiler plate setup that appears in all of my tests, but at least the mock itself is just implemented in one place.

Finally, if you do decide to create *.mock.ts files for your services, they need to be registered somewhere to get your AOT build to work. I created a separate module just for unit testing, where all of my mock services are registered. (this isn’t necessary in Angular 6 CLI-generated projects which have separate tsconfig files for testing. in that case just add *.mock.ts to all the same places you see *.spec.ts configured in both tsconfig files).

One other thing I noticed, by carefully analyzing my base components that I use, is that for the most part these could be replaced with a base service instead. There isn’t really much benefit that my component gets from being composed with a base component. All my base is really doing is calling things on services. So, if I was going to write my app all over again, I would try to avoid component inheritance altogether, and if I think I need any common functionality I would place that into a service or interface instead.

Another important technique we have is TypeScript Interfaces. If we notice a group of components is similar, we can create an Interface that describes the common functionality. Then when we go to make a new component we easily conform to the contract by implementing the Interface.

3reactions
FirstVertexcommented, Aug 16, 2018

Closing this issue, since this does not need attention from Angular team.

The bottom line is, Angular doesn’t have a way to replace a base class with a mock. But, we shouldn’t need to. Instead, we can use spies for methods on the base component, and/or we can use mocks for the dependencies of the sub class and the base class.

Read more comments on GitHub >

github_iconTop Results From Across the Web

failed to run unit test for component that extends a base class ...
I am trying to write unit test for a component that extends a base class. component.ts export class DesktopPresentationAreaComponent extends ...
Read more >
Angular Testing Extended Classes - StackBlitz
Starter project for Angular testing. ... export class AbstractDataService {. constructor() { }. parentMethod() {. console.log('parent method');.
Read more >
Inheritance in unit tests - Vladimir Khorikov
Test them indirectly, via the concrete classes that inherit from the abstract class. That's because tests should view domain classes as a black...
Read more >
Component testing scenarios - Angular
A component-under-test doesn't have to be injected with real services. In fact, it is usually better if they are test doubles such as,...
Read more >
Base class - Unit Testing in C#
Base class · Especially in older codebases, services and components can be part of a deep hierarchy of classes. · Each mock offers...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found