Simply running the application I get no error and it works just fine, but when I run my tests I get the following error:
'pattern-list' is not a known element:
1. If 'pattern-list' is an Angular component, then verify that it is part of this module.
2. If 'pattern-list' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schema' of this component to suppress this message. ("
[ERROR ->]<pattern-list></pattern-list>
I had this issue first when I just run the application with 'npm-start' and I solved it adding the required component to app.module in the declarations section. But now as I want to test I get the same error and I don't know why.
Here is my code:
app.module.ts
#NgModule({
imports: [ BrowserModule, FormsModule, HttpModule, ReactiveFormsModule ],
declarations: [ AppComponent, PatternListComponent, PatternDetailComponent, WidgetListComponent,
FormComponent, DefaultWidget, LabelComponent, CheckboxWidget ],
bootstrap: [ AppComponent ],
providers: [ WidgetService ]
})
export class AppModule { }
app.component.ts
#Component({
selector: 'my-app',
template: `
<pattern-list></pattern-list>
`
})
export class AppComponent { }
pattern.list.component:
#Component({
selector: 'pattern-list',
template: `
<div class="patterns">
<pattern-detail *ngFor="let p of patternDetails" [metadata]="p"
(selectPattern)="selectPattern(p)"></pattern-detail>
</div>
<div *ngIf="selectedPattern" class="widget-list">
<widget-list [pattern]="selectedPattern">
</widget-list>
</div>
`,
styleUrls: ['/css/styles.css']
})
export class PatternListComponent implements OnInit{
selectedPattern: PatternDetails;
constructor(private http: Http) {
}
patternDetails: PatternDetails[];
ngOnInit() {
this.getPatterns();
}
getPatterns() {
this.http.get('/app/assets/patternDetails.json')
.map((res:Response) => res.json() )
.subscribe(
data => { this.patternDetails = data.patternList; },
err => console.error('The problem is: ' + err),
() => console.log('done')
);
console.log(this.patternDetails);
}
selectPattern(pattern: PatternDetails) {
this.selectedPattern = pattern;
this.setSelectedProperty(pattern);
}
setSelectedProperty(selectedPattern: PatternDetails) {
for (var p in this.patternDetails) {
if (this.patternDetails[p] == selectedPattern) {
this.patternDetails[p].selected = true;
} else {
this.patternDetails[p].selected = false;
}
}
}
}
My test file: app.component.spec.ts
describe('AppComponent with TCB', function () {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [AppComponent]});
});
describe('asdfasdf', function () {
beforeEach(async(() => {
TestBed.compileComponents();
}));
it('should instantiate component', () => {
let fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent');
});
});
});
I'm using webpack, I'm not sure if that matters.
I think you need
TestBed.configureTestingModule({imports: [AppModule]});
The current approach to avoid these errors when testing components is to make their tests shallow. As per the official docs:
Add NO_ERRORS_SCHEMA to the testing module's schemas metadata to tell the compiler to ignore unrecognized elements and attributes. You no longer have to declare irrelevant components and directives.
So you can simply import NO_ERRORS_SCHEMA and add it to your testing module config:
import { NO_ERRORS_SCHEMA } from '#angular/core';
TestBed.configureTestingModule({
schemas: [ NO_ERRORS_SCHEMA ]
})
But be aware of:
Shallow component tests with NO_ERRORS_SCHEMA greatly simplify unit testing of complex templates. However, the compiler no longer alerts you to mistakes such as misspelled or misused components and directives.
As micronyks mentined in his answer I need to add my other dependencies in the declarations of configureTestingModule. So if I modify my module configuration in the test like this:
TestBed.configureTestingModule({declarations: [AppComponent,PatternListComponent]});
it'll work. It seems you need to add every dependency in the configureTestingModule declaration.
Related
I have been struggling with this for a while, and I'm hoping someone can help. I have a component that uses a service to get data. I am attempting to add unit tests to it. My problem is that the tests always fail with "Error: No provider for Http". Here is my code:
Service:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import { Contact } from './contact.model';
#Injectable()
export class ContactsService {
constructor(private http: Http) { }
public getContacts(): Observable<Array<Contact>> {
return this.http.get('assets/contacts.json').map(res => {
let r = res.json().Contacts;
return r;
});
}
}
Component:
import { Component, OnInit, NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { Contact } from '../contact.model';
import { ContactsService } from '../contacts.service';
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
providers: [ContactsService]
})
export class ContactsComponent implements OnInit {
contactsAll: Array<Contact>;
contacts: Array<Contact>;
constructor(private contactsService: ContactsService) { }
ngOnInit() {
this.contactsService.getContacts().subscribe((x) => {
this.contactsAll = x;
this.contacts = this.contactsAll;
});
}
}
Tests:
import { async, ComponentFixture, TestBed, inject } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { By } from '#angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { ContactsComponent } from './contacts.component';
import { ContactsService } from '../contacts.service';
import { Contact } from '../contact.model';
class MockContactsService extends ContactsService {
constructor() {
super(null);
}
testContacts: Array<Contact> = [
new Contact("test1 mock", 12345, 10000),
new Contact("test2 mock", 23456, 20000)
];
public getContacts(): Observable<Array<Contact>> {
return Observable.of(this.testContacts);
}
}
describe('ContactsComponent', () => {
let component: ContactsComponent;
let fixture: ComponentFixture<ContactsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [ContactsComponent],
// providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
}).overrideComponent(ContactsService, {// The following is to override the provider in the #Component(...) metadata
set: {
providers: [
{ provide: ContactsService, useClass: MockContactsService },
]
}
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContactsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('1st test', () => {
it('true is true', () => expect(true).toBe(true));
})
});
Let's try this:
First, move your providers array from your component to your NgModule. It's better to provide your services at the module level since it de-couples your providers from your component tree structure (unless you specifically want to have a separate instance of a provider per component, and from your simplified use case, there's no need for a separate instance per component).
so,
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
/// providers: [ContactsService] <-- remove this line
})
export class ContactsComponent implements OnInit {
.....
and add it to the NgModule that declares your ContactsComponent
#NgModule({
imports: ..
declarations: ...
providers: [ContactsService] // <-- provider definition moved to here
})
export class ModuleDeclaringContactsComponent
Once you do that, then mocking the ContactsService in your test is easy to implement.
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [ContactsComponent],
providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
});
With that, you should be good to go.
Sorry everyone - turns out it was something completely different.
I modified my code as per snorkpete's answer, and I am going to mark that as the answer, as I believe that is the cleanest approach.
The real problem came from using Angular Cli to create my project. It automatically created tests for my component and my service. This meant the code in the service test was causing the error, not the code in the component. I commented out the code in the service test and everything passed.
Annoyingly, there was no indication in any of the failures that this is where the error was coming from!
In case if component should have Service in providers we can ovverride metadata by TestBed.overrideComponent():
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
providers: [ContactsService] // IF YOU NEED IT
})
we need to do next:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [ SomeComponent ],
providers: [
{provide: SomeService, useValue: SomeMock},
provideMockStore({system: {userConfig: {viewSettings: {theme: ThemeName.light}}}} as any)
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
TestBed.overrideComponent(SomeComponent , { set: { providers: [{provide: SomeService, useValue: SomeMock}]}})
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent );
component = fixture.componentInstance;
fixture.detectChanges();
});
more info here https://codecraft.tv/courses/angular/unit-testing/dependency-injection/
Created a new angular 2 project with angular-cli
Below is the default component app.component.ts and it has app.component.spec.ts
import { Component } from '#angular/core';
import { AngularFire } from 'angularfire2'; // import angularfire2
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
af: AngularFire;
constructor(af: AngularFire){
this.af=af;
this.firebaseCall();
}
//push data to firebase collection
firebaseCall(){
let post=this.af.database.list('/post');
post.push({a:'test'});
}
}
To Implement Unit test for the above firebaseCall() in app.component.spec.ts
I have added/updated below lines in app.component.spec.ts
import { AngularFire } from 'angularfire2';
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,AngularFire // extra added
],
});
TestBed.compileComponents();
});
I get below error while ng test
Unexpected value 'AngularFire' declared by the module 'DynamicTestModule'
You need to mock the services in the test.
Here you are injecting AngularFire. So you test setup should be like this.
import { AngularFire } from 'angularfire2';
...
const mockFirebase = jasmine.createSpyObj('af',['database']);
af.database.and.returnValue({list: Rx.Observable.of([])});
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
providers:[
{provide:AngularFire, useValue:}]
});
TestBed.compileComponents();
});
Hope, this helps you. always mock your providers in the unit-test and don't make a http call.
In my app's root component, I am defining custom SVG icons for md-icon. When unit testing a component that displays the custom icon I get an error. It seems that the error is likely due to the fact that my root component is not being used/initialized in my child unit test.
Is there a way to mock or add these custom icons (or md-icon) when setting up the test module? I would simply define the icons in the component I am testing, but I know other components will need them also.
The error:
Uncaught Error: Error in ./CustomerComponent class CustomerComponent - inline template:34:19 caused by: __WEBPACK_IMPORTED_MODULE_4_rxjs_Observable__.Observable.throw is not a function
Full error:
Removing the custom icons from the template solves the error.
My template is using the custom icons like this:
<md-icon svgIcon="vip">vip</md-icon>
And the root component initializes the icons like this:
this.iconRegistry.addSvgIcon(
'vip',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);
I set up the test component like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: CUSTOMER,
company: COMPANY,
}),
},
},
},
{
provide: UtilityService,
useClass: UtilityServiceMock,
},
// etc...
],
declarations: [
CustomerComponent,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
})
.compileComponents();
}));
Versions
Angular 2.3.0
Material 2.0.0-beta.1
I was able to use the overrideModule method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue where Angular team members discuss how to override declarations. The idea is to remove the component from the MdIconModule so that we can declare our own mock icon component.
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestedComponent ],
imports: [
RouterTestingModule.withRoutes([]),
SharedModule,
],
})
.overrideModule(MdIconModule, {
remove: {
declarations: [MdIcon],
exports: [MdIcon]
},
add: {
declarations: [MockMdIconComponent],
exports: [MockMdIconComponent]
}
})
.compileComponents();
}));
The MockMdIconComponent is defined very simply
#Component({
selector: 'md-icon',
template: '<span></span>'
})
class MockMdIconComponent {
#Input() svgIcon: any;
#Input() fontSet: any;
#Input() fontIcon: any;
}
I used this approach because I am not importing the Material modules individually and I did not want my test to have to make Http calls to get the svg icons. The MockMdIconComponent could be declared in the testing module but I chose to declare/export it in the module override so that I could extract the object into a test helper.
Answering my own question:
After much trial/error with items like mocking the MdIconRegistry or using componentOverride() etc with no luck I no longer use a shared module within my tests.
Instead, I declare the MdIcon component directly in my testing module using a mock version of the class.
describe(`CustomerComponent`, () => {
let component: CustomerComponent;
let fixture: ComponentFixture<CustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
MdButtonModule,
],
providers: [
OVERLAY_PROVIDERS,
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: customer,
company: COMPANY,
}),
},
params: Observable.of({
customerId: customerId,
}),
},
},
],
declarations: [
CustomerComponent,
// Declare my own version of MdIcon here so that it is available for the CustomerComponent
MdIconMock,
],
});
fixture = TestBed.createComponent(CustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it(`should exist`, () => {
expect(component).toBeTruthy();
});
});
MdIconMock is simply a blank class that matches the selectors:
import { Component } from '#angular/core';
#Component({
template: '',
// tslint:disable-next-line
selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
Note: Due to TSLint rules that specify the prefix/format of class names/selectors I needed to disable TSLint for this mock.
This is a late answer. Just in case anyone come across this, there is an alternative other than OP's solution (which is nice as well):
Imports MaterialModule using forRoot()
TestBed.configureTestingModule({
declarations: [
TestComponent
],
imports: [SharedModule, MaterialModule.forRoot()],
providers: [{ provide: Router, useValue: routerStub }]
});
TestBed.compileComponents();
Get the injected MdIconRegistry and DomSanitizer
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);
Configure them as you did in normal app
iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));
building on #Chic's answer, since I have icons everywhere, i made a testbed patch:
import {TestBed} from '#angular/core/testing';
import {MatIconModule, MatIcon} from '#angular/material/icon';
import {Component, Input} from '#angular/core';
export function PatchTestBedMatIcons() {
const original = TestBed.configureTestingModule;
TestBed.configureTestingModule = (moduleDef) => {
return original(moduleDef)
.overrideModule(MatIconModule, {
remove: {
declarations: [MatIcon],
exports: [MatIcon]
},
add: {
declarations: [MockMatIconComponent],
exports: [MockMatIconComponent]
}
});
};
}
#Component({
selector: 'mat-icon',
template: '<span></span>'
})
class MockMatIconComponent {
#Input() svgIcon: any = null;
#Input() fontSet: any = null;
#Input() fontIcon: any = null;
}
then in your component test simply:
import {PatchTestBedMatIcons} from 'src/app/patchTestBedIcons';
PatchTestBedMatIcons();
I am trying to write some unit-tests on a component that got some services injected into it, to load the data from server. Data is loaded in this component on OnInit() method. I am trying that service method returns some dummy data, using spyOn. Following is unit-test setup -
let comp: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let staticDataService: any;
let spy: jasmine.Spy;
let allCountries: string[];
describe('MyComponent', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
imports : [ FormsModule, HttpModule ],
declarations : [MyComponent],
providers: [ StaticDataService ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
comp = fixture.componentInstance;
staticDataService = fixture.debugElement.injector.get(StaticDataService);
allCountries = [] = ["US", "UK"];
spy = spyOn(staticDataService, 'getCountries').and.returnValue(Promise.resolve(allCountries));
});
it('Countries should be set', () => {
expect(comp.allCountries).toEqual(allCountries);
});
});
Following is the component class that I am unit-testing -
#Component({
moduleId: module.id,
selector: 'myeditor',
templateUrl: 'my.component.html',
styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
allCountries: string[];
constructor(private _staticDataServices: StaticDataService) {}
ngOnInit() {
this.getDataFromServer();
}
getDataFromServer()
{
this.allCountries = this._staticDataServices.getCountries();
}
I am getting the following error -
Chrome 53.0.2785 (Windows 7 0.0.0) MyComponent Countries should be set FAILED
[1] Expected undefined to equal [ 'US', 'UK' ].
Under the same unit-tests few other tests are working fine, that are not dependent on injected services. Getting 'undefined' while testing the properties that are set by services.
Can someone please help what I am doing wrong here?
Thanks
You need to call fixture.detectChanges() for the ngOnInit to be called.
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
getCountries returns a Promise so you need to then it, otherwise the value of allCountries will just be promise and not the data
getDataFromServer() {
this._staticDataServices.getCountries().then(data => {
this.countries = data;
});
}
Since the promise is asynchronous, you need to use async and wait for the asynchronous task to complete by calling fixture.whenStable()
import { async } from '#angular/core/testing';
it('...', async(() => {
fixture.whenStable().then(() => {
expect(comp.allCountries).toEqual(allCountries);
})
})
UDPATE
Without seeing the StaticDataService, I'm guessing you are trying to inject Http into it. This wont work in a test environment without further configuration. What I suggest you do is just make the service a mock
staticDataService = {
getCountries: jasmine.createSpy('getCountries').and.returnValue(...);
}
providers: [
{ provide: StaticDataService, useValue: staticDataService }
]
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')
};
);