How to mock natively component dependencies
Hey, I know there are many documentation about this but it seems to not be effective. Why? People does not read documentation… Every time I start a new mission, the developers don’t test their components or do it wrong. In the last case, they never mock their component dependencies and that’s a problem because it’s not a unit test anymore.
I think a little reminder is necessary.
Context
I’m developing a CancelButtonComponent. It’s a material button with a specific text and specific mat-button properties.
I’m working on a standalone component.
I’m using jest and snapshot testing to be sure that the genereted HTML is as expected.
How to
@Component({
selector: 'app-cancel-button',
standalone: true,
template: `<button mat-button color="primary">Cancel</button>`,
imports: [MatButtonModule]
})
export class CancelButton {}
It’s a really simple component and here we just have to control the generated HTML.
Here’s the spec file:
describe('CancelButtonComponent', () => {
let component: CancelButtonComponent;
let fixture: ComponentFixture<CancelButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CancelButtonComponent],
}).compileComponents();
fixture = TestBed.createComponent(CancelButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(fixture.nativeElement).toMatchSnapshot();
});
});
Here I don’t mock anything.
That’s the generated snapshot:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CancelButtonComponent should create 1`] = `
<div
id="root0"
>
<button
class="mdc-button mat-mdc-button mat-primary mat-mdc-button-base"
color="primary"
mat-button=""
>
<span
class="mat-mdc-button-persistent-ripple mdc-button__ripple"
/>
<span
class="mdc-button__label"
>
Cancel
</span>
<span
class="mat-mdc-focus-indicator"
/>
<span
class="mat-ripple mat-mdc-button-ripple"
matripple=""
/>
<span
class="mat-mdc-button-touch-target"
/>
</button>
</div>
`;
I don’t think this snapshot is really relevant. I don’t care about MatButton specific css classes, I don’t care about all generated spans and that make a big snapshot just for one line of HTML to test. Snapshot is a part of your code it MUST be reviewed.
Now, let’s do better with a mock.
Here’s a simple mock for the MatButton:
@Component({
selector: '[mat-button]',
standalone: true,
template: ` <ng-content></ng-content>`,
})
class MatButtonMock {}
I use the projection () to let the button’s text appear in the snapshot.
And that’s how to use it in the test:
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CancelButtonComponent],
})
.overrideComponent(CancelButtonComponent, {
remove: {
imports: [MatButtonModule],
},
add: {
imports: [MatButtonMock],
},
})
.compileComponents();
...
});
We just tell to TestBed to remove the import of MatButtonModule for CancelButtonComponent and to replace it with our MatButtonMock.
Let’s see the snapshot now:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CancelButtonComponent should create 1`] = `
<div
id="root0"
>
<button
color="primary"
mat-button=""
>
Cancel
</button>
</div>
`;
It’s so better. I see only the specifities of my CancelButtonComponent:
- usage of a mat-button
- set the color to primary
- set the text to “Cancel”
It’s more readable, it’s more reviewable and it’s more relevant.
Angular offers other methods to override a component in a test:
- overrideComponent
- overridePipe
- overrideDirective
- overrideModule
- overrideProviders
I put here the full spec file if you need it:
describe('CancelButtonComponent', () => {
let component: CancelButtonComponent;
let fixture: ComponentFixture<CancelButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CancelButtonComponent],
})
.overrideComponent(CancelButtonComponent, {
remove: {
imports: [MatButtonModule],
},
add: {
imports: [MatButtonMock],
},
})
.compileComponents();
fixture = TestBed.createComponent(CancelButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(fixture.nativeElement).toMatchSnapshot();
});
});
Conclusion
Never mind, you are using jest, Karma/jasmine or another testing tool. You always must mock your dependencies (components, services, pipes…) to avoid to be impacted by a third party in your test. And also because we are speaking about unit test.
If you need more examples or other testing use case, let me know in comment. I will try to help you.
Thanks for reading.
Top comments (0)