-
-
Notifications
You must be signed in to change notification settings - Fork 85
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
Issue in mocking service provided in directive in submodule #623
Comments
Hi there, thanks for the report. I'll take a look next days at the issue. |
Hi @DavideCanton, I think the issue is in the next circumstances:
Therefore, the If we consider the circumstances, we can make it work like that:
import {MockBuilder, MockRenderFactory, ngMocks} from 'ng-mocks';
import { TodoModule } from 'app/todo/todo.module';
import { FormsModule } from '@angular/forms';
import { fakeAsync, tick } from '@angular/core/testing';
import { TodoComponent } from 'app/todo/todo/todo.component';
import { ContextService } from 'app/shared/services/context.service';
import { SharedModule } from 'app/shared/shared.module';
fdescribe('TodoComponent', () =>
{
ngMocks.faster();
beforeAll(() => MockBuilder(TodoComponent, TodoModule)
.keep(FormsModule)
.mock(SharedModule));
let factory: MockRenderFactory<never>;
beforeAll(() => factory = MockRenderFactory(`<app-todo [appContext]="null"></app-todo>`));
fit('should create', fakeAsync(() =>
{
const fixture = factory(null, false);
const ctx = ngMocks.get(fixture.point, ContextService);
ctx.todo = { id: 1, description: 'aaa', done: false };
fixture.detectChanges();
expect(fixture.point.componentInstance).toBeTruthy();
}));
}); However, in my option, this test looks more like an integration test between import {MockBuilder, MockRenderFactory, ngMocks} from 'ng-mocks';
import {TodoModule} from 'app/todo/todo.module';
import {FormsModule} from '@angular/forms';
import {TodoComponent} from 'app/todo/todo/todo.component';
import {SharedModule} from 'app/shared/shared.module';
import {ContextDirective} from 'app/shared/directives/context.directive';
fdescribe('TodoComponent', () =>
{
ngMocks.faster();
beforeAll(() => MockBuilder(TodoComponent, TodoModule)
.keep(FormsModule)
.keep(ContextDirective)
.mock(SharedModule));
let factory: MockRenderFactory<never>;
beforeAll(() => factory = MockRenderFactory(`<app-todo [appContext]="todo"></app-todo>`));
fit('should create', () =>
{
const fixture = factory({
todo: { id: 1, description: 'aaa', done: false },
});
expect(fixture.point.componentInstance).toBeTruthy();
});
}); |
Hi, sorry for the late answer. I don't agree with the premise of your analysis, because I'm not relying in my In my opinion |
Hi there, I think, the best way to understand the problem is to try to implement this test in pure angular and The thing here is about how DI works. If nobody provided If you don't want to care who provides
Then it can be accessed in a global scope outside of |
I see no problems in testing with TestBed: import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { DescriptionComponent } from 'app/shared/description/description.component';
import { ContextService } from 'app/shared/services/context.service';
import { TodoComponent } from 'app/todo/todo/todo.component';
import { CommonModule } from '@angular/common';
import { Spectator } from '@ngneat/spectator';
fdescribe('TodoComponent', () =>
{
let component: TodoComponent;
let fixture: ComponentFixture<TodoComponent>;
let ctx: ContextService;
beforeEach(async(() =>
{
TestBed.configureTestingModule({
declarations: [TodoComponent, DescriptionComponent],
imports: [
FormsModule
],
providers: [
{
provide: ContextService,
useValue: jasmine.createSpyObj(ContextService, ['remove'])
}
]
})
.compileComponents();
}));
beforeEach(() =>
{
fixture = TestBed.createComponent(TodoComponent);
component = fixture.componentInstance;
ctx = TestBed.inject(ContextService);
});
it('should create', fakeAsync(() =>
{
ctx.todo = { id: 1, description: 'aaa', done: false };
fixture.detectChanges();
tick();
expect(component).toBeTruthy();
}));
it('should display item correctly if not done', fakeAsync(() =>
{
ctx.todo = { id: 1, description: 'aaa', done: false };
fixture.detectChanges();
tick();
expect(fixture.debugElement.query(By.css('.id')).nativeElement.innerHTML).toBe('1.');
expect(fixture.debugElement.query(By.css('input')).nativeElement.checked).toBe(false);
}));
it('should display item correctly if done', fakeAsync(() =>
{
ctx.todo = { id: 2, description: 'bbb', done: true };
fixture.detectChanges();
tick();
expect(fixture.debugElement.query(By.css('.id')).nativeElement.innerHTML).toBe('2.');
expect(fixture.debugElement.query(By.css('input')).nativeElement.checked).toBe(true);
}));
it('should update todo if checkbox clicked', fakeAsync(() =>
{
ctx.todo = { id: 2, description: 'bbb', done: false };
fixture.detectChanges();
tick();
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.click();
fixture.detectChanges();
tick();
expect(fixture.debugElement.query(By.css('input')).nativeElement.checked).toBe(true);
expect(ctx.todo.done).toBe(true);
}));
it('should emit if remove clicked', fakeAsync(() =>
{
ctx.todo = { id: 2, description: 'bbb', done: false };
fixture.detectChanges();
tick();
const button = fixture.debugElement.query(By.css('button'));
button.nativeElement.click();
fixture.detectChanges();
tick();
expect(ctx.remove).toHaveBeenCalledTimes(1);
}));
}); This is because I can provide a mock to |
Ha, in this example, instead of mocking If it is the original idea, then you should do the same in the original test and simply use Mocking and providing are different actions. The first one turns providers into mocks at places of their definition, whereas the second one adds them in the global context despite their place of definition. |
What I think is that it might be a problem with MockBuilder(TodoComponent, [TodoModule, ContextService]) would add |
Ok, then maybe I misunderstood the function of the |
Maybe, I think it can be a bit confusing and not fully clear. What In my option, this creates an ambiguity: if What I plan to do with v13 is to deprecate this behavior and consider all But this all should be properly rethought and defined. Sorry for inconvenience, I hope |
Hi @DavideCanton, I hope you are doing great. I've added a fix for such a case. After the release, you could use const module = MockBuilder(
[TodoComponent, FormsModule], // things to keep
[TodoModule, SharedModule, ContextService], // things to mock and export
// the exported things are guaranteed to be available form any context.
).build(); or with const module = MockBuilder(TodoComponent, TodoModule)
.keep(FormsModule)
.mock(SharedModule)
.mock(ContextService, ContextService, {export: true})
.build(); |
fix(mock-builder): provides globally exported providers from directives and components #623
v12.1.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. |
Thanks @satanTime , I'll try it. :) |
Hi,
I'm trying to unit test a component (
TodoComponent
), injecting a service (ContextService
). The service comes from the providers of a directive (ContextDirective
) instantiating the component dynamically and providing the service as a "context".The directive is declared and exported from module
SharedModule
and provides the service:The component is declared in the
TodoListModule
, which imports theSharedModule
, and injects the ContextService, which is a private instance for the component decorated with the directive:The setup I'm using for testing the component is the following (I'm using spectator, but I already tried without it and the outcome is the same):
I get the error
NullInjectorError: No provider for ContextService!
, because it seems that the MockBuilder is not mocking the service.I tried also adding
.mock(ContextService)
, with imho should be sufficient because the component shouldn't know who is the provider of the service, it just needs an instance, but the error persists.If I remove the
.mock(SharedModule)
and I mock explicitly theContextService
it works, but I get a lot of warnings because a subcomponent (DescriptionComponent
) defined inSharedModule
is not mocked, and I would like that it and all the other dependencies fromSharedModule
are mocked.I tried debugging the issue, and it seems that the
.mock(ContextService)
line is ignored because the check in https://github.com/ike18t/ng-mocks/blob/master/libs/ng-mocks/src/lib/mock-builder/promise/add-missed-mock-declarations-and-modules.ts#L14 aborts the mocking process forContextService
because it is already present in thengMocksUniverse.touches
Set.Here (https://github.com/DavideCanton/ng-mocks-issue) there is the repo for reproducing the issue, the tests not working are the ones for the
TodoComponent
, and they are already marked withfdescribe
, so anpm run test
is sufficient to reproduce the issue.Am I missing something in configuring the
MockBuilder
?The text was updated successfully, but these errors were encountered: