DEV Community

Gaëtan Redin
Gaëtan Redin

Posted on • Originally published at Medium on

Angular testing

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 {}
Enter fullscreen mode Exit fullscreen mode

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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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>
`;
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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();

    ...
  });
Enter fullscreen mode Exit fullscreen mode

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>
`;
Enter fullscreen mode Exit fullscreen mode

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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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.

Learn more

Top comments (0)