help-me-mom / ng-mocks

Angular testing library for mocking components, directives, pipes, services and facilitating TestBed setup
https://www.npmjs.com/package/ng-mocks
MIT License
1.06k stars 81 forks source link

Bug: MockComponent (Standalone) mocks all components not just the ones specified #8649

Open Kogs opened 8 months ago

Kogs commented 8 months ago

Description of the bug

Using MockComponent to mock one dependent child Component all other child Components are mocked as well.

An example of the bug

If you have total 3 components (All Components are Standalone): MyComp
MyComp1
MyComp2
And you want to Test MyComp but mock MyComp1 and keep the real MyComp2. The following example will result in all Components (MyComp1 & MyComp2) to get Mocked for some reason.

<app-my-comp1></app-my-comp1>
<app-my-comp2></app-my-comp2>
describe('MyComp', () => {
 beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [
         MockComponent(MyComp1)
      ],
      imports: [
          MyComp
      ]
    }).compileComponents();
   ....
});

After a lot of try and error I figured out how to solve this.
Workaround:

await TestBed.configureTestingModule({
  declarations: [
    MockComponent(MyComp1),
    MyComp2
  ],
  imports: [
      MyComp
  ]
}).compileComponents();

By adding the MyComp2 to the declarations everything works as expected.
I also tried to move declarations into the imports section like it's suggested for Standalone components, but this didn't make any difference.

Angular: 17.1.2 (Standalone Components) NgMocks: 14.12.1

Expected vs actual behavior

I would expect that only the specified Components that I put into MockComponent are replaced with Mock variants not everything else?

GipHub123 commented 7 months ago

@Kogs

To test standalone components, try setting up your test cases using MockBuilder; Check example: https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestStandaloneComponent/test.spec.ts


beforeEach(async () => {

  return MockBuilder(MyComp)
    .keep(MyComp1)
    .mock(MyComp2);
});

Here is another real life example;

beforeEach(async () => {

  return MockBuilder(ButtonActionComponent)
    .keep(MatButtonModule)
    .keep(MatIconModule)
    .keep(MatTooltipModule)
    .keep(TranslateModule)
    .mock(ComponentX);

});

You want to usually isolate test case meaning that better practice is to mock all components that are not necessary for your test case. In another words use should probably mock MyComp2 as well :)

screambeard commented 7 months ago

@GipHub123 thanks for the explanation, because I'm also encountering this issue. If I understand correctly, MockComponent and MockComponents mocks all components in the target component independently of the arguments for those functions?

Your comment about that you should probably mock MyComp2 as well, can be true, but is largely dependent of the test your are writing. Also using the MockBuilder forces you to rewrite the tests to work with the MockBuilder.

It would make more sense that MockComponent and MockComponents only mocks the actual components provided. So curious to hear from the maintainer whether this is a bug or not or what is the best way to work around this is using MockBuilder is not an option.

GipHub123 commented 7 months ago

Your comment about that you should probably mock MyComp2 as well, can be true, but is largely dependent of the test your are writing. Also using the MockBuilder forces you to rewrite the tests to work with the MockBuilder.

@screambeard Yes I totally agree with you 👍 I was trying to say to that mocking makes usually more sense.

Also using the MockBuilder forces you to rewrite the tests to work with the MockBuilder. Yes. Rewriting tests setup can be haunting task. Been there 😄