How to provide a mock for a Standalone component in a cypress component test?
I have this mount-function inside my test:
function mount() {
cy.mount(TestComponent, {
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{ provide: TestService, useValue: mockTestService },
],
});
}
The TestComponent
looks like this:
@Component({
selector: 'app-test',
standalone: true,
imports: [TestModule],
templateUrl: './test.component.html',
styleUrl: './test.component.scss',
})
export class TestComponent {
testService = inject(TestService);
}
The TestModule
provides the TestService
and some other services it depends on (which I don't need for my test, since I want to use a mock).
While the module looks like this:
@NgModule({
providers: [TestService, OtherService],
})
export class TestModule {}
The problem is, that it doesn't work. The test still calls the original service instead of the mock.
The basic problem is that TestService
is provided via TestModule
.
Ideally there should be a way to mock TestModule
, but I couldn't find it.
However I did find a way to remove TestModule
so that your mockTestService
if effective.
Essentially, use the TestBed.overrideComponent() method to do so.
This is the complete test. Normally TestService.getValue()
returns 42, but the mock service returns 33.
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {TestBed} from '@angular/core/testing'
import {TestComponent} from './test.component'
import {TestService} from './test.service'
import {TestModule} from './test.module'
it('mocks TestService.getValue', async () => {
class MockTestService {
getValue() { return 33 } // mocked value, should show in template
}
const mockTestService = new MockTestService;
await TestBed.configureTestingModule({ imports: [TestComponent]})
.overrideComponent(TestComponent, {
remove: {
imports: [TestModule], // remove TestModule from component
},
})
.compileComponents();
cy.mount(TestComponent, {
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{ provide: TestService, useValue: mockTestService },
],
})
cy.get('h1')
.should('have.text', 'I am TestComponent: 33') // check mock is used
})
With the mock removed, the failing test log:
I had to infer the component code from the information in the question - I haven't used Angular since version 5 and the docs have gaps that I had to guess at, so the component I used may not be exactly the same as yours.
It does not feel quite right to remove the TestModule
, I would prefer to mock it. That would probably entail creating MockTestModule
which uses MockTestService
and import it into cy.mount()
. Unfortunately I havn't the time to check that out.
test.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class TestService {
constructor() {}
getValue() {
return 42; // "real" value shown in template if no mock is used
}
}
test.component.ts
import {Component} from '@angular/core';
import {TestModule} from './test.module'
import {TestService} from './test.service'
@Component({
selector: 'app-test',
standalone: true,
imports: [TestModule],
template: `<h1>I am TestComponent: <span>{{value}}</span></h1>`
})
export class TestComponent {
value = 0;
constructor(
private testService: TestService
) {
this.value = testService.getValue()
}
}
test.module.ts
import { provideHttpClient } from '@angular/common/http';
import {NgModule} from '@angular/core';
import {TestService} from './test.service'
@NgModule({
providers: [
provideHttpClient(),
{provide: TestService}
],
})
export class TestModule {}