I have a angular component, using a class that I want to mock in the unit test. So I override this in the provider-section of my test. But the component still calls the real class and I get an error.
This worked fine in older projects without standalone components, but I think I need to do something different for standalone components? But I couldn't figure out what :-(
I simplified it to this test case:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule, ModalController } from '@ionic/angular';
import { BrowserModule } from '@angular/platform-browser';
import { Component } from '@angular/core';
// Dummy Standalone Component, just a single method that uses the ionic ModalController
@Component({
selector: 'app-test-dialog',
template: '',
imports: [
IonicModule
],
standalone: true
})
export class TestDialogComponent {
constructor(private modalCtrl: ModalController) {
}
async close() {
await this.modalCtrl.dismiss();
}
}
// Test, should override the dismiss method of the ModalController
describe('TestDialogComponent', () => {
let component: TestDialogComponent;
let fixture: ComponentFixture<TestDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
BrowserModule,
IonicModule.forRoot(),
TestDialogComponent
],
providers: [
{
// provide our own ModalController
provide: ModalController, useValue: {
dismiss: () => Promise.resolve()
}
}
]
}).compileComponents();
fixture = TestBed.createComponent(TestDialogComponent);
component = fixture.componentInstance;
});
// this should call the provided mock class
// but I get 'overlay does not exist thrown'
// because it calls the real method :-(
it('close closes the dialog', async () => {
const modalController = TestBed.inject(ModalController);
const dismissSpy = spyOn(modalController, 'dismiss');
await component.close();
expect(dismissSpy).toHaveBeenCalled();
});
});
I found the following solution. But I don't get why I need this in this case and in other cases the overridden providers work...
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule, ModalController } from '@ionic/angular';
import { BrowserModule } from '@angular/platform-browser';
import { Component } from '@angular/core';
@Component({
selector: 'app-test-dialog',
template: '',
imports: [
IonicModule
],
standalone: true
})
export class TestDialogComponent {
constructor(private modalCtrl: ModalController) {
}
async close() {
await this.modalCtrl.dismiss();
}
}
// use a mock-class so we can directly spy on it
class ModalControllerMock {
dismiss() {
return Promise.resolve();
}
}
describe('TestDialogComponent', () => {
let component: TestDialogComponent;
let fixture: ComponentFixture<TestDialogComponent>;
const modalControllerMock = new ModalControllerMock();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
BrowserModule,
IonicModule.forRoot(),
TestDialogComponent
]
}).compileComponents();
// use this provider instead of the provider in configureTestingModule
TestBed.overrideComponent(TestDialogComponent, {
set: {
providers: [{ provide: ModalController, useValue: modalControllerMock}],
},
});
fixture = TestBed.createComponent(TestDialogComponent);
component = fixture.componentInstance;
});
it('close closes the dialog', async () => {
const dismissSpy = spyOn(modalControllerMock, 'dismiss');
await component.close();
expect(dismissSpy).toHaveBeenCalled();
});
});