I've created an offcanvas component for angular, but I can't get my unit tests to work.
Here's the failing unit test (offcanvas-host.component):
describe('BsOffcanvasHostComponent', () => {
let component: BsOffcanvasTestComponent;
let fixture: ComponentFixture<BsOffcanvasTestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ CommonModule, OverlayModule ],
declarations: [
// Unit to test
BsOffcanvasHostComponent,
// Mock dependencies
BsOffcanvasMockComponent,
BsOffcanvasHeaderMockComponent,
BsOffcanvasBodyMockComponent,
BsOffcanvasContentMockDirective,
// Testbench
BsOffcanvasTestComponent,
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BsOffcanvasTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
type OffcanvasPosition = 'top' | 'bottom' | 'start' | 'end';
@Component({
selector: 'bs-offcanvas-test',
template: `
<bs-offcanvas [(show)]="isOffcanvasVisible" [position]="position" [hasBackdrop]="true" (backdropClick)="isOffcanvasVisible = false">
<div *bsOffcanvasContent>
<bs-offcanvas-header>
<h5>Offcanvas</h5>
</bs-offcanvas-header>
<bs-offcanvas-body>
<span>Content</span>
</bs-offcanvas-body>
</div>
</bs-offcanvas>`
})
class BsOffcanvasTestComponent {
isOffcanvasVisible = false;
position: OffcanvasPosition = 'start';
}
@Directive({ selector: '[bsOffcanvasContent]' })
class BsOffcanvasContentMockDirective {
constructor(offcanvasHost: BsOffcanvasHostComponent, template: TemplateRef<any>) {
offcanvasHost.content = template;
}
}
@Component({
selector: 'bs-offcanvas-holder',
template: `
<div>
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>`,
providers: [
{ provide: BsOffcanvasComponent, useExisting: BsOffcanvasMockComponent }
]
})
class BsOffcanvasMockComponent {
constructor(@Inject(OFFCANVAS_CONTENT) contentTemplate: TemplateRef<any>) {
this.contentTemplate = contentTemplate;
}
contentTemplate: TemplateRef<any>;
}
@Component({
selector: 'bs-offcanvas-header',
template: `
<div class="offcanvas-header">
<ng-content></ng-content>
</div>`
})
class BsOffcanvasHeaderMockComponent {}
@Component({
selector: 'bs-offcanvas-body',
template: `
<div class="offcanvas-body">
<ng-content></ng-content>
</div>`
})
class BsOffcanvasBodyMockComponent {}
which tests the following component:
ngAfterViewInit() {
const injector = Injector.create({
providers: [
{ provide: OFFCANVAS_CONTENT, useValue: this.content },
],
parent: this.rootInjector,
});
const portal = new ComponentPortal(BsOffcanvasComponent, null, injector);
const overlayRef = this.overlayService.create({
scrollStrategy: this.overlayService.scrollStrategies.block(),
positionStrategy: this.overlayService.position().global()
.top('0').left('0').bottom('0').right('0'),
hasBackdrop: false
});
this.component = overlayRef.attach<BsOffcanvasComponent>(portal); // <-- The test fails here
this.component.instance.backdropClick
.pipe(takeUntil(this.destroyed$))
.subscribe((ev) => {
this.backdropClick.emit(ev);
});
this.viewInited$.next(true);
}
The error message is
Error: NG0302: The pipe 'async' could not be found in the 'BsOffcanvasComponent' component!. Find more at https://angular.io/errors/NG0302
Here's a minimal reproduction of the issue
How can tell the angular TestingModule to use the mock component instead of the initial component type when calling
overlayRef.attach<BsOffcanvasComponent>(portal)
command in my unit test?
EDIT
Sadly I'm still not getting it to work. Usually for unit testing in angular there are mainly 3 cases:
Components referenced through the use of the tagname in the template of the UTT (unit-to-test)
This you solve by creating a MockComponent with the same tagname and inputs/outputs.
Ancestoral components injected in the UTT
This you solve by creating a MockComponent with a provide
r on the decorator
@Component({
selector: 'bs-offcanvas-holder',
template: ``,
providers: [
{ provide: BsOffcanvasComponent, useExisting: BsOffcanvasMockComponent }
]
})
class BsOffcanvasMockComponent {}
Component types called directly from the UTT
this.component = overlayRef.attach<BsOffcanvasComponent>(portal);
Here I should be able to use the BsOffcanvasMockComponent
instead, without having the unit-test dragging the other file in the TestBed. So how can I solve this? Off course I can mock the CDK Overlay
service, but this still leaves me with the above line of code in my UTT, where the BsOffcanvasComponent
is litterally being dragged into the Testbed.
I was able to solve the problem by providing a factory in my runtime module (OffcanvasModule) and in my TestingModule. This eliminates the
import
of the BsOffcanvasComponent in the testingmodule.components.module.ts
offcanvas-host.component.spec.ts
This solves the following error