I'm getting the follow error when I run Angular 2 unit test using webpack:
FAILED TESTS:
Other Trims Tested
✖ should display other trims articles
PhantomJS 2.1.1 (Linux 0.0.0)
Error: Template parse errors:
Unexpected closing tag "div" ("module.exports = "<div *ngFor=\"let trim of otherTrims\">\n {{ trim.name }}\n[ERROR ->]</div>\n\n<!--<div class='test-name'>{{ otherTrims[0].name }}</div>-->\n";"): OtherTrimsTestedComponent#0:78 in /app/config/spec-bundle.js (line 52805)
I see that it is testing webpacks bundle-js file but I am not sure if there is another import I have to do from angular core tests
I have my HTML setup as:
<div *ngFor="let trim of otherTrims">
{{ trim.name }}
</div>
and spec test as:
import {
it,
fdescribe,
expect,
inject,
async,
TestComponentBuilder
} from '#angular/core/testing';
import { OtherTrimsTestedComponent } from './other-trims-tested.component';
let mockData = [
{
'featured_image': 'http://test.url.com',
'article_type': 'Test',
'article_url': 'http://test.url.com',
'name': 'Ford F-150 Raptor',
'specs' : '4 Door AWD Pickup Truck, 150Bhp, 285 lb-ft, 8-sp Automatic',
'msrp': '50,000'
}
];
fdescribe('Other Trims Tested', () => {
it('should have a selector', () => {
let annotations = Reflect.getMetadata('annotations', OtherTrimsTestedComponent);
expect(annotations[0].selector).toBe('other-trims-tested');
});
it('should display other trims articles', async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(OtherTrimsTestedComponent).then(fixture => {
// Get components
let otherTrimsTestedComponent = fixture.componentInstance; // Get component instance
let element = fixture.nativeElement; // Get test component elements
otherTrimsTestedComponent.otherTrims = mockData;
// Detect changes? (How?)
fixture.detectChanges();
// Test against data
expect(element.querySelector('.test-name').innerText).toBe('Ford F-150 Raptor2');
});
})));
});
and Component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'other-trims-tested',
template: require('./other-trims-tested.component.html')
})
export class OtherTrimsTestedComponent implements OnInit {
#Input() otherTrims: any[];
constructor() { }
ngOnInit() { }
}
Any hints/articles can help. Thanks
Have you added html-loader to your webpack test configuration?
rules: [
...
{
test: /\.html$/,
loader: 'raw-loader'
}
...
}
Related
I have header component definition as following:
import { Component, OnChanges, Input } from '#angular/core';
#Component({
selector: 'app-section-header',
template:`
<div class="pageTitle">
<h1>{{name}}</h1>
<a class="editBtn" [routerLink]="routerLink">edit</a>
</div>
<div class="progress"></div>
`,
styleUrls: ['./section-header.component.css']
})
export class SectionHeaderComponent implements OnChanges {
public routerLink: string[];
#Input() name: string;
ngOnChanges() {
this.routerLink = ['/section', this.name, 'edit'];
}
}
this component gets binding 'name' from its parent component, later it used to form a part of routeLink to 'edit' screen.
It is working well when running application.
For some reason, I cannot test the correct creation of this link:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { SectionHeaderComponent } from './section-header.component';
import { RouterTestingModule } from '#angular/router/testing';
import { Component, Input, Injectable, OnChanges , SimpleChanges, Output,SimpleChange, EventEmitter} from '#angular/core'
fdescribe('SectionHeaderComponent', () => {
let component: SectionHeaderComponent;
let fixture: ComponentFixture<SectionHeaderComponent>;
let element, de;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [SectionHeaderComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SectionHeaderComponent);
component = fixture.componentInstance;
element = fixture.nativeElement; // to access DOM element
de = fixture.debugElement;
});
it('should create link to edit view', () => {
component.name='sasha';
fixture.detectChanges();
component.ngOnChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('h1').innerText).toBe('Sasha');
//for some reason this test failing with error expected '/'is not
// equal to 'section/sasha/edit'
expect(de.query(By.css('a')).nativeElement.getAttribute('href')).toBe ('/section/sasha/edit');
});
});
});
Where am I go wrong?
Thanks
You need to call fixture.detectChanges() after the call to ngOnChanges(). After making this change, it should work
Plunker
it('should create link to edit view', () => {
component.name = 'sasha';
component.ngOnChanges();
fixture.detectChanges()
expect(element.querySelector('h1').innerText).toBe('sasha');
expect(de.query(By.css('a')).nativeElement.getAttribute('href'))
.toBe('/section/sasha/edit');
});
I am trying to test a component that renders background images whenever elements are visible. I detect visibility on scroll an resize events, and to ease the load I'm using debounceTime from RxJS. This however is a problem since the unit test fails with
Failed: Cannot use setInterval from within an async zone test.
I know this is a problem due to the async nature of debounceTime, but I'm not sure how to prevent this / mock debounceTime for the test.
This is the Component
import { Component } from '#angular/core';
import { EventEmitter } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import * as config from '../../config';
import ListComponentBase from '../../../forminputs/ListComponentBase';
#Component({
selector: `list-component`,
template: `
<a
*ngFor="let item of data.list"
class="box"
routerLink="/edit/{{item._id}}">
<div class="name" >{{ item.name }}</div>
<list-item-background
*ngIf="isVisible[item._id]" [mediaObjectId]="item.coverImageUrl"></list-item-background>
</a>
`,
inputs: ['data', 'id'],
outputs: ['filter', 'sorting'],
})
export class ListComponent extends ListComponentBase {
constructor() {
this.isVisible = {};
}
ngOnInit() {
this.check();
if (document.querySelector('nav')){
this.scroll = Observable
.fromEvent(document.querySelector('nav'), 'scroll')
.debounceTime(100).subscribe((event) => {
this.checkVisibility();
});
}
this.resize = Observable
.fromEvent(window, 'resize')
.debounceTime(100).subscribe((event) => {
this.checkVisibility();
});
}
check() {
this.data.list.forEach(item => {
this.isVisible[item._id] = this.isElementInViewport(document.querySelector(`[data-id="${item._id}"]`));
});
}
isElementInViewport(el) {
if (!el) return false;
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
);
}
}
This is the unit test
import { Router } from '#angular/router';
import { TestBed, inject, async } from '#angular/core/testing';
import { StoreModule } from '#ngrx/store';
import RouterLinkMockModule from '../../../testing/RouterLinkMock.module';
import { ListComponent } from './list.component';
import { defaultData, collectionName } from '../../config';
const data = {
sort: initialState.sort,
list: [defaultData, defaultData],
articles: { null: { name: 'test' } },
};
describe(`${collectionName} ListComponent`, () => {
let fixture;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterLinkMockModule,
],
declarations: [
ListComponent,
],
});
});
it('should render 2 items in list', async(inject([Router], (router) => {
fixture = TestBed.createComponent(ListComponent);
fixture.componentInstance.data = data;
fixture.detectChanges();
const el = fixture.debugElement.nativeElement;
expect(el.querySelectorAll('.box').length).toBe(2);
})));
});
You can swap out the default scheduler used by debounceTime with a TestScheduler. It will then no longer schedule future values using setInterval. I am not 100% sure if your test will succeed without the debounce running, otherwise you will have to make a true Rx test in which you let the TestScheduler run in virtual time so it will reach the desired end state as if your debounce had been triggered IRL
I had the very same problem and what helped was using the jasmine's 'done'. Though I must admit I am not familiar with the inject and I do not know what versions of angular, etc. you are running, I am trying to adapt your code, in the way that it worked for me:
...
describe(`${collectionName} ListComponent`, () => {
let fixture;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterLinkMockModule,
],
declarations: [
ListComponent,
],
providers: [
Router //I think you have to do that here if you do not use the inject
]
});
});
it('should render 2 items in list', (done) => {
fixture = TestBed.createComponent(ListComponent);
fixture.componentInstance.data = data;
fixture.detectChanges();
const el = fixture.debugElement.nativeElement;
expect(el.querySelectorAll('.box').length).toBe(2);
done();
});
...
I am not a 100% sure that this will work for you - I have another method that stores all the references I need in a class called 'Page', like it is suggested on the angular.io page for testing. In there I also call create component and I think that is the critical part that should be covered by the 'done'.
Not sure if that is relevant, but for reference: I am using angular 2.3.1 and karma 1.2.0 with jasmine-core 2.5.2
I can't seem to test a component that uses a Date pipe in Angular 2 (using Karma through PhantomJS). When I try, I get ORIGINAL EXCEPTION: ReferenceError: Can't find variable: Intl
Here's my entire spec file:
import { provide, PLATFORM_PIPES } from '#angular/core';
import { DatePipe } from '#angular/common';
import { addProviders, async, inject } from '#angular/core/testing';
import { Post, PostComponent, PostHtmlComponent } from './';
import { usingComponentFixture } from '../../test-helpers';
describe('Component: Post', () => {
beforeEach(() => {
provide(PLATFORM_PIPES, {useValue: DatePipe, multi: true });
addProviders([PostComponent, PostHtmlComponent, ]);
});
it('should render an h1 tag with text matching the post title',
usingComponentFixture(PostComponent, fixture => {
let component = <PostComponent>fixture.componentInstance;
let element = fixture.nativeElement;
component.post = <Post>{ title: 'Hello', publishedOn: new Date('8/5/2016') };
fixture.detectChanges();
expect(element.querySelector('.blog-post-header h1').innerText).toBe('Hello');
})
);
});
And this is the component template:
<div class="col-lg-8 col-md-7 col-sm-6">
<h1>{{post.title}}</h1>
<p class="lead">{{post.publishedOn | date:'fullDate'}}</p>
</div>
I was able to resolve this issue. Here's what I had to do:
npm install karma-intl-shim --save-dev
Add 'intl-shim' to the frameworks collection in karma.conf.js
Add the following to karma-test-shim.js (this is referenced in the files collection of karma.conf.js)
require('karma-intl-shim');
require('./en-us.js'); // copied from https://github.com/andyearnshaw/Intl.js/blob/master/locale-data/json/en-US.json
Intl.__addLocaleData(enUsLocaleData);
Instead of mocking the DatePipe, you can use the transform method of DatePipe in typescript which is equivalent to the | operator in the HTML file
import {DatePipe} from '#angular/common';
let pipe = new DatePipe('en');
expect(page.myDate.nativeElement.innerHTML).toBe(pipe.transform(model.date, 'dd/MM/yyyy');
For tests I mock date pipe:
#Pipe({
name: 'date',
pure: false // required to update the value when the promise is resolved
})
export class MockedDatePipe implements PipeTransform {
name: string = 'date';
transform(query: string, ...args: any[]): any {
return query;
}
}
Then when I configure testing module I inject it into declaration:
TestBed.configureTestingModule( {
providers: [
SelectionDispatcher,
{ provide: MyService, useClass: MockedMyServiceService }
],
declarations: [ MyComponent, MockedTranslatePipe, MockedDatePipe ]
});
That worked for me:
import { DatePipe, registerLocaleData } from '#angular/common';
import localeDe from '#angular/common/locales/de';
registerLocaleData(localeDe);
//..
describe('My Test', () => {
let pipe = new DatePipe('de-DE');
it('My Test-Case', () => {
expect(page.myDate.nativeElement.innerHTML).toBe(pipe.transform(model.date);
});
});
You must set the right locale.
That is a snippet from a Cypress-Test.
that's what worked for me:
import {DatePipe} from "#angular/common";
...
TestBed.configureTestingModule({
...
providers: [DatePipe]
...
});
Expanding on other answers on here I was using the DatePipe in my component to produce a payload. I had the following setup.
Return the transform method on DatePipe in the mock, matching parameters used by the component i.e. ('YY'). Otherwise we will just get undefined as the value when testing.
.spec file
import { DatePipe } from '#angular/common';
.....
const mockDatePipe = {
transform: jest.fn((val) => new DatePipe('en').transform(val, 'YY')),
};
.....
beforeEach(() => {
component = new TestComponent(
(mockDatePipe as unknown) as DatePipe,
.....
);
});
it('should return correct payload', () => {
expect(component.getPayload(new Date('2022-02-02')).toEqual(
{
purchaseYear: '22',
}
}
.ts file
public getPayload(date: new Date(), .....){
return {
purchaseYear: this.datePipe.transform(date, 'YY')
};
);
How do I mock sub component in jasmine tests?
I have MyComponent, which uses MyNavbarComponent and MyToolbarComponent
import {Component} from 'angular2/core';
import {MyNavbarComponent} from './my-navbar.component';
import {MyToolbarComponent} from './my-toolbar.component';
#Component({
selector: 'my-app',
template: `
<my-toolbar></my-toolbar>
{{foo}}
<my-navbar></my-navbar>
`,
directives: [MyNavbarComponent, MyToolbarComponent]
})
export class MyComponent {}
When I test this component, I do not want to load and test those two sub components; MyNavbarComponent, MyToolbarComponent, so I want to mock it.
I know how to mock with services using provide(MyService, useClass(...)), but I have no idea how to mock directives; components;
beforeEach(() => {
setBaseTestProviders(
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
);
//TODO: want to mock unnecessary directives for this component test
// which are MyNavbarComponent and MyToolbarComponent
})
it('should bind to {{foo}}', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(MyComponent).then((fixture) => {
let DOM = fixture.nativeElement;
let myComponent = fixture.componentInstance;
myComponent.foo = 'FOO';
fixture.detectChanges();
expect(DOM.innerHTML).toMatch('FOO');
});
});
Here is my plunker example;
http://plnkr.co/edit/q1l1y8?p=preview
As requested, I'm posting another answer about how to mock sub components with input/output:
So Lets start by saying we have TaskListComponent that displays tasks, and refreshes whenever one of them is clicked:
<div id="task-list">
<div *ngFor="let task of (tasks$ | async)">
<app-task [task]="task" (click)="refresh()"></app-task>
</div>
</div>
app-task is a sub component with the [task] input and the (click) output.
Ok great, now we want to write tests for my TaskListComponent and of course we don't want to test the real app-taskcomponent.
so as #Klas suggested we can configure our TestModule with:
schemas: [CUSTOM_ELEMENTS_SCHEMA]
We might not get any errors at either build or runtime, but we won't be able to test much other than the existence of the sub component.
So how can we mock sub components?
First we'll define a mock directive for our sub component (same selector):
#Directive({
selector: 'app-task'
})
class MockTaskDirective {
#Input('task')
public task: ITask;
#Output('click')
public clickEmitter = new EventEmitter<void>();
}
Now we'll declare it in the testing module:
let fixture : ComponentFixture<TaskListComponent>;
let cmp : TaskListComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TaskListComponent, **MockTaskDirective**],
// schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: TasksService,
useClass: MockService
}
]
});
fixture = TestBed.createComponent(TaskListComponent);
**fixture.autoDetectChanges();**
cmp = fixture.componentInstance;
});
Notice that because the generation of sub component of the fixture is happening asynchronously after its creation, we activate its autoDetectChanges feature.
In our tests, we can now query for the directive, access its DebugElement's injector, and get our mock directive instance through it:
import { By } from '#angular/platform-browser';
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
[This part should usually be in the beforeEach section, for cleaner code.]
From here, the tests are a piece of cake :)
it('should contain task component', ()=> {
// Arrange.
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
// Assert.
expect(mockTaskEl).toBeTruthy();
});
it('should pass down task object', ()=>{
// Arrange.
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
// Assert.
expect(mockTaskCmp.task).toBeTruthy();
expect(mockTaskCmp.task.name).toBe('1');
});
it('should refresh when task is clicked', ()=> {
// Arrange
spyOn(cmp, 'refresh');
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
// Act.
mockTaskCmp.clickEmitter.emit();
// Assert.
expect(cmp.refresh).toHaveBeenCalled();
});
If you use schemas: [CUSTOM_ELEMENTS_SCHEMA]in TestBed the component under test will not load sub components.
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { TestBed, async } from '#angular/core/testing';
import { MyComponent } from './my.component';
describe('App', () => {
beforeEach(() => {
TestBed
.configureTestingModule({
declarations: [
MyComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
});
it(`should have as title 'app works!'`, async(() => {
let fixture = TestBed.createComponent(MyComponent);
let app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Todo List');
}));
});
This works in the released version of Angular 2.0.
Full code sample here.
An alternative to CUSTOM_ELEMENTS_SCHEMA is NO_ERRORS_SCHEMA
Thanks to Eric Martinez, I found this solution.
We can use overrideDirective function which is documented here,
https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html
It takes three prarmeters;
1. Component to implement
2. Child component to override
3. Mock component
Resolved solution is here at http://plnkr.co/edit/a71wxC?p=preview
This is the code example from the plunker
import {MyNavbarComponent} from '../src/my-navbar.component';
import {MyToolbarComponent} from '../src/my-toolbar.component';
#Component({template:''})
class EmptyComponent{}
describe('MyComponent', () => {
beforeEach(injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.overrideDirective(MyComponent, MyNavbarComponent, EmptyComponent)
.overrideDirective(MyComponent, MyToolbarComponent, EmptyComponent)
.createAsync(MyComponent)
.then((componentFixture: ComponentFixture) => {
this.fixture = componentFixture;
});
));
it('should bind to {{foo}}', () => {
let el = this.fixture.nativeElement;
let myComponent = this.fixture.componentInstance;
myComponent.foo = 'FOO';
fixture.detectChanges();
expect(el.innerHTML).toMatch('FOO');
});
});
I put together a simple MockComponent module to help make this a little easier:
import { TestBed } from '#angular/core/testing';
import { MyComponent } from './src/my.component';
import { MockComponent } from 'ng2-mock-component';
describe('MyComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent,
MockComponent({
selector: 'my-subcomponent',
inputs: ['someInput'],
outputs: [ 'someOutput' ]
})
]
});
let fixture = TestBed.createComponent(MyComponent);
...
});
...
});
It's available at
https://www.npmjs.com/package/ng2-mock-component.
I'm trying to use fakeAsync to test an Angular 2 component, but the fixture variable is not being set. In fact, the promise callback is not being called. Here is the code:
#Component({
template: '',
directives: [GroupBox, GroupBoxHeader]
})
class TestComponent {
expandedCallback() { this.expandedCalled = true; }
}
it('testing...', inject([TestComponentBuilder], fakeAsync((tcb) => {
var fixture;
tcb.createAsync(TestComponent).then((rootFixture) => {
fixture = rootFixture
});
tick();
fixture.detectChanges();
})));
When I run this code, I get:
Failed: Cannot read property 'detectChanges' of undefined
TypeError: Cannot read property 'detectChanges' of undefined
I can't figure out why the callback isn't fired. In this repository, it works fine: https://github.com/juliemr/ng2-test-seed/blob/master/src/test/greeting-component_test.ts
Any clue?
Note: I'm using ES6, Traceur, Angular 2 beta, Karma and Jasmine.
------ UPDATE ------
It follows a repository with the failing test:
https://github.com/cangosta/ng2_testing_fakeasync
TestComonentBuilder doesn't work with templateUrl https://github.com/angular/angular/issues/5662
Try this way
https://github.com/antonybudianto/angular2-starter/blob/master/app/simplebind/child.component.spec.ts#L15
The point is you create a test dummy component (TestComponent for example) and register the component you want to test in directives: [...] and use template: <my-cmp></my-cmp>, then pass the TestComponent to tsb.createAsync(TestComponent)..., and use injectAsync.
I prefer this way since I can easily mock the data from parent, and pass any input and handle output to/from the component.
import {
it,
injectAsync,
describe,
expect,
TestComponentBuilder,
ComponentFixture
} from 'angular2/testing';
import { Component } from 'angular2/core';
import { ChildComponent } from './child.component';
#Component({
selector: 'test',
template: `
<child text="Hello test" [(fromParent)]="testName"></child>
`,
directives: [ChildComponent]
})
class TestComponent {
testName: string;
constructor() {
this.testName = 'From parent';
}
}
let testFixture: ComponentFixture;
let childCompiled;
let childCmp: ChildComponent;
describe('ChildComponent', () => {
it('should print inputs correctly', injectAsync([TestComponentBuilder],
(tsb: TestComponentBuilder) => {
return tsb.createAsync(TestComponent).then((fixture) => {
testFixture = fixture;
testFixture.detectChanges();
childCompiled = testFixture.nativeElement;
childCmp = testFixture.debugElement.children[0].componentInstance;
expect(childCompiled).toBeDefined();
expect(childCmp).toBeDefined();
expect(childCompiled.querySelector('h6'))
.toHaveText('From parent');
expect(childCompiled.querySelector('h5'))
.toHaveText('Hello test');
});
}));
it('should trigger changeMe event correctly', () => {
childCmp.changeMe();
testFixture.detectChanges();
expect(childCmp.num).toEqual(1);
expect(childCompiled.querySelector('h6'))
.toHaveText('Changed from child. Count: ' + childCmp.num);
});
});