Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular: How to mock extends class in unit test

I've a injectable service (EntityApi) which extends a class (BaseApi). In my spec I like to mock the BaseApi with BaseApiStub. But it vain. Always calling the EntityApi.

// class
export class BaseApi { // want to mock BaseApi
    constructor(injector: Injector) {
       console.log("Should not be here...");
    }
}

// service
@Injectable()
export class EntityApi extends BaseApi {
    constructor(injector: Injector) {
       super(injector, "entity");
    }
}

// component
@Component({
  selector: 'rt-entity-list',
  templateUrl: './entity-list.component.html',
})
export class EntityListComponent {
  api: any;
  constructor(public entityApi: EntityApi) { 
    this.api = entityApi;
  }
}

// mock api
export class BaseApiStub { //mocked api
    constructor() {
      console.log("You are on track!!")
    }
    get() { }
}

// spec
describe('EntityListComponent', () => {
  let component: EntityListComponent;
  let fixture: ComponentFixture<EntityListComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [EntityListComponent],
      providers: [ { provide: BaseApi, useClass: BaseApiStub }, // mocked class.
      ],
      schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
    
  beforeEach(() => {
    fixture = TestBed.createComponent(EntityListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

Expected Behavior is, while compile component in spec. It should call the BaseApiStub, instead it is calling BaseApi. I've seen a solution as below. But no luck.

export class BaseApiStub extends BaseApi { }

Test Code: stackblitz Check the console. I expect the You are on track!! log but received as Should not be here...

Not able to progress further. Can someone correct my mistake please.

like image 382
Premkumar Jayaseelan Avatar asked Jun 01 '18 09:06

Premkumar Jayaseelan


1 Answers

What you are trying to do does not work. Dependency injection and class inheritance are not directly related. This means you cannot switch out the base class of your service like this.

As I see it you have two ways on how to do this.

Option 1:

Instead of mocking your BaseApi and providing the mock in your test you need to mock your EntityApi and provide this mock in your test.

Option 2:

Instead of letting your EntityApi extend from BaseApi, you could keep BaseApi a simple service and provide it as a dependency.

Instead of

class EntityApi extends BaseApi {
    constructor(private injector: Injector) {

you do

class EntityApi {
    constructor(private api: BaseApi) {

If you setup your EntityApi like this, it does not extend from BaseApi, but rather has it as a dependency. Then you can create a mock of the BaseApi and provide it like you did in your test.

Edit

Regarding your comment:

Since I should be using methods from BaseApi I cannot go without extends.

This is not true. Let's say the BaseApi has a method foo() that you want to use. When you extend your baseclass, the usage might look like this:

class EntityApi extends BaseApi {
    constructor(private injector: Injector) {}
    exampleMethod() {
        this.foo();
    }
}

If you just have the dependency you can still call the method like this:

class EntityApi {
    constructor(private api: BaseApi) {}
    exampleMethod() {
        this.api.foo();
    }
}

You don't need to extend from BaseApi in order to call methods on it.

like image 104
tom van green Avatar answered Oct 10 '22 04:10

tom van green