Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ngMocks#findInstance not working for ViewContainerRef#createComponent case example #1596

Closed
LukasMachetanz opened this issue Jan 14, 2022 · 13 comments · Fixed by #1778
Closed

Comments

@LukasMachetanz
Copy link

LukasMachetanz commented Jan 14, 2022

Hi everyone!

As the title states I am presenting a simplified case example using ViewContainerRef#createComponent, where ngMocks#findInstance does not return the created instance. I am wondering if this is expected or a bug. Any help appreciated.

Let's assume having the following ParentComponent, which will be the Subject Under Test (SUT).

@Component({
   template: ""
})
export class ParentComponent {
   constructor(private readonly viewContainerRef: ViewContainerRef) {
      this.viewContainerRef.createComponent(ChildComponent);
   }
}

And let's assume having the following ChildComponent.

@Component({
   template: ""
})
export class ChildComponent {}

The corresponding test suite could look something like this:

describe("AppComponent", () => {
  let component: ParentComponent;
  let fixture: ComponentFixture<ParentComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ ParentComponent, MockComponent(ChildComponent)]
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('', () => {
    console.log(ngMocks.findInstance(ChildComponent)); // The instance of ChildComponent cannot be found.
    console.log(fixture.debugElement.query(By.directive(ChildComponent))); // The instance can be found.
  });
});

ngMocks.findInstance(ChildComponent) does not return the instance created with this.viewContainerRef.createComponent(ChildComponent). fixture.debugElement.query(By.directive(ChildComponent)) finds the dynamically created component, which indicates that the mock could be use properly. The only problem is ngMocks#findInstance, which does not return the obviously available instance.

As I stated before, I am wondering if this is intended or a bug. Thanks.

@satanTime
Copy link
Member

Hi,

thanks for the report. This one will be the first fix right after the stable release.

@satanTime
Copy link
Member

satanTime commented Jan 23, 2022

Hi, happy Sunday!

Works well for me on v13.0.0.

import { Component, ViewContainerRef } from '@angular/core';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';

@Component({
  selector: 'parent',
  template: '',
})
class ParentComponent {
  public constructor(viewContainerRef: ViewContainerRef) {
    const vcr: any = viewContainerRef;
    if (vcr.createComponent) {
      vcr.createComponent(ChildComponent);
    }
  }
}

@Component({
  selector: 'child',
  template: '',
})
class ChildComponent {}

describe('issue-1596', () => {
  beforeEach(() => MockBuilder(ParentComponent, ChildComponent));

  it('finds child component', () => {
    MockRender(ParentComponent);
    expect(
      ngMocks.findInstance(ChildComponent, undefined),
    ).toBeDefined();
  });
});

@satanTime
Copy link
Member

satanTime commented Jan 23, 2022

Ah, it doesn't work via TestBed. Checking that.

Aha, a nice one, thanks! I'll be working on its fix.

@LukasMachetanz
Copy link
Author

LukasMachetanz commented Jan 23, 2022

I guess the TestBed isn't involved at all, as ChildComponent is just directly used within the source code. Neither Angular's Dependency Injection nor its "rendering approach" is involved here. As far as I understand the factory of ɵcmp is used, which is added by the Angular Compiler.

I am not sure if this information helps though; probably it is just confusing. If you need more information I can elaborate on it. I also have other case examples which probably have the same root cause.

I am not 100% sure where the mocked component instances are registered within ng-mocks, but I guess the modification has to be made there.

@satanTime
Copy link
Member

what I found so far is the problem of the root level.

for example, MockRender(ParentComponent) doesn't have that problem, because it's rendered in the root instead of being rendered as the root.

you could try to use it as a temp solution, and i'll be working on a proper fix for it.

@satanTime
Copy link
Member

Hi there,

I think there is a good question to discuss. In the rendered HTML child component is actually outside of current fixture.
I'm checking options to fix it, and I wanted to share that finding here.

<div id="root0" ng-version="13.2.1">
  <div>parent</div>
</div>
<child>
  <span>child</span>
</child>

@satanTime
Copy link
Member

The desired behavior works only in ivy mode.

satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 5, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 5, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 6, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 6, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 6, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 6, 2022
satanTime added a commit to satanTime/ng-mocks that referenced this issue Feb 6, 2022
satanTime added a commit that referenced this issue Feb 6, 2022
 fix: looking in vcr.createComponent on root node #1596
@satanTime
Copy link
Member

v13.0.1 has been released and contains a fix for the issue. Feel free to reopen the issue or to submit a new one if you meet any problems.

@LukasMachetanz
Copy link
Author

@satanTime First of all I would like to thank you for resolving the issue.

Secondly, I would like to ask you for a favour: Can you probably point me to the source code where the instance of the mocked component is actually registered? I assume that ngMocks uses some kind of registry. In general, I am very curious how this library works behind the scenes. Unfortunately I could not grasp it with the source code alone. I guess that explaining the whole concept is probably to time consuming, but maybe you can share some thoughts.

Furthermore, it would be cool if this repository would provide a "Discussion" channel, where topics like this could be tackled.

Greets,
Lukas

@satanTime
Copy link
Member

Hi @LukasMachetanz,

the registry is on Angular's side. ng-mocks simply walks through DOM and tries to check whether the Injector of the current element returns the desired instance.

Source code is a mess, and with every fix I make it more messy.
It's consequences of open source and the limited time for fixes :)
Every time I submit a PR, I think that would never hire myself :)

Answering your question:

@satanTime
Copy link
Member

Hi @ike18t,

long time no see, happy new year!

Could you enable "Discussion" in the repo?

@ike18t
Copy link
Collaborator

ike18t commented Feb 27, 2022

Hey @satanTime !

I think you are good to go!

@satanTime
Copy link
Member

Great thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment