I am working on upgrading an Angular 1 app to Angular 2 using #angular/upgrade. For the most part this has gone well. I now have an Angular 2 component that works within my Angular 1 app when manually testing.
However, so far I've been unable to successfully unit test the Angular 2 component. My code looks like the following:
app.ts
import "./polyfills";
import "./app.helpers";
import * as angular from "angular";
import "reflect-metadata";
import {UpgradeAdapter, UpgradeAdapterRef} from "#angular/upgrade";
import { NgModule } from "#angular/core";
import {BrowserModule} from "#angular/platform-browser";
import {initRoutes} from "./app.routes";
import {initComponents, components} from "./app.components";
import {initServices, services} from "./app.services";
import {initHttpConfig} from "./app.http-config";
#NgModule({
imports: [BrowserModule],
declarations: [].concat(components),
providers: [].concat(services)
})
class AppModule {}
export const upgradeAdapter = new UpgradeAdapter(AppModule);
const app = angular.module("App", [/*ng1 modules*/]);
initHttpConfig(app);
initRoutes(app);
initComponents(app);
initServices(app);
angular.element(document).ready(() => {
upgradeAdapter.bootstrap(document.body, ["App"]);
});
export default app;
export const ng = angular;
spec-entry.ts
import "./polyfills";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/proxy.js";
import "zone.js/dist/sync-test";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import {getTestBed} from "#angular/core/testing";
import {BrowserTestingModule, platformBrowserTesting} from "#angular/platform-browser/testing";
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
interface WebpackRequire extends NodeRequire {
context(dir: string, includeSubdirs: boolean, matchFiles: RegExp) : any;
}
const wpRequire = require as WebpackRequire;
const testsContext = wpRequire.context(".", true, /\.spec\.ts$/);
testsContext.keys().forEach(testsContext);
my-component.ts
import "../app.ts";
import {
async,
inject,
TestBed,
} from "#angular/core/testing";
import * as moment from "moment";
import {CollegeEvent} from "../models/college-event.model";
import {MyComponent} from "./my.component";
describe("My component", function () {
let fixture;
let component;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.debugElement.componentInstance;
});
it("should not throw an error", () => {
});
});
When running my tests I receive the following error:
1) Error: No provider for TestingCompilerFactory!
Any help would be greatly appreciated.
This may be due to multiple node_modules folders in your client directory. It can be present inside your node_modules folder or in other folder.This happens sometimes when you copy the code or unzip the node_modules folder from outside and another duplicate folder is created.
Angular would have been checking inside node_modules for testing purpose and might have found multiple occurrence for the same.
The main problem is that you need to declare a provider for your all services that are in your component in your beforeEach.
Try the following code in your spec. I'm not sure what TestingCompilerFactory is in your app, but you may have to mock it. Check out the docs.
let comp: MyComponent;
let fixture: ComponentFixture<MyComponent>;
describe("My component", () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [
// DECLARE PROVIDERS HERE
{ provide: TestingCompilerFactory }
]
}).compileComponents()
.then(() => {
fixture = TestBed.createComponent(MyComponent);
comp = fixture.componentInstance;
});
}));
it("should not throw an error", () => {
// not sure how you are storing your errors, this is just an example spec
expect(comp.errors).toEqual([]);
}));
});
Related
I want to use scrollTo() function of window object. Directly by accessing window.scrollTo(0,0), i can achieve this functionality. When i googled about using window in angular, people are creating a provider like below one:
import {InjectionToken, FactoryProvider} from '#angular/core';
export const WINDOW = new InjectionToken<Window>('window');
const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: () => window,
};
export const WINDOW_PROVIDERS = [windowProvider];
Inside service using like the below code:
import {Injectable, Inject} from '#angular/core';
import {WINDOW} from './window.provider';
#Injectable({
providedIn: 'root',
})
export class WindowService {
constructor(#Inject(WINDOW) public window: Window) {}
}
In module
{provide: WINDOW_PROVIDERS, useValue: window},
Everything works with the above code, but while running test cases i m getting the below error. Almost half of the application test cases are failing with below error
NullInjectorError: No provider for InjectionToken window!
Default Test case for window.service
import { TestBed } from '#angular/core/testing';
import { WindowService } from './window.service';
describe('WindowService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: WindowService = TestBed.get(WindowService);
expect(service).toBeTruthy();
});
});
How to fix this??
You need to set the following configureTestingModule:
TestBed.configureTestingModule({
providers: [WINDOW_PROVIDERS, WindowService]
});
I followed official angular-cli tutorial to integrate angular-universal to my existing angular-cli app.
I am able to do SSR for my angular-cli app. But when I try to integrate ngx-leaflet, I am getting following error:
ReferenceError: navigator is not defined
at D:\ng2-ssr-pwa\dist\server.js:40251:29
Now, I understand that leaflet is trying to access navigator object which is not available in the Node context. So I decided to delay leaflet rendering until the page is loaded in the browser as given in this SO thread.
But still I am getting same error. You can look the demo app with leaflet issue here.
./src/app/browserModuleLoader.service.ts:
import { Component, Inject, Injectable, OnInit, PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Injectable()
export class BrowserModuleLoaderService {
private _L: any;
public constructor(#Inject(PLATFORM_ID) private _platformId: Object) {
this._init();
}
public getL() {
return this._safeGet(() => this._L);
}
private _init() {
if (isPlatformBrowser(this._platformId)) {
this._requireLegacyResources();
}
}
private _requireLegacyResources() {
this._L = require('leaflet');
}
private _safeGet(getCallcack: () => any) {
if (isPlatformServer(this._platformId)) {
throw new Error('invalid access to legacy component on server');
}
return getCallcack();
}
}
./src/app/leaflet/app/leaflet.component.ts:
// import * as L from 'leaflet';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '#angular/core';
import { BrowserModuleLoaderService } from '../browserModuleLoader.service';
import { isPlatformBrowser } from '#angular/common';
#Component({
selector: 'app-leaflet',
styleUrls: ['./leaflet.component.scss'],
template: `
<div *ngIf="isBrowser">
<div leaflet [leafletOptions]="options"></div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LeafletComponent {
isBrowser: boolean;
options = {};
constructor(private cdr: ChangeDetectorRef,
#Inject(PLATFORM_ID) platformId: Object,
private browserModuleLoaderService: BrowserModuleLoaderService
) {
this.isBrowser = isPlatformBrowser(platformId);
}
ngAfterViewInit() {
console.log('this.isBrowser ', this.isBrowser);
if (this.isBrowser) {
const L = this.browserModuleLoaderService.getL();
this.options = {
layers: [
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
],
zoom: 5,
center: L.latLng({ lat: 38.991709, lng: -76.886109 }),
};
}
this.cdr.detach();
}
}
./src/app/app.component.html:
<div>
<app-leaflet></app-leaflet>
</div>
How do I safely delay the leaflet rendering until the platform is not browser?
EDIT:
I removed all code related to leaflet (browserModuleLoader.service.ts, leaflet.component.ts ect. ) and kept only leaflet module import in app.module.ts and actually this import is causing issue.
./src/app/app.module.ts:
import { AppComponent } from './app.component';
import { BrowserModule } from '#angular/platform-browser';
// import { BrowserModuleLoaderService } from './browserModuleLoader.service';
// import { LeafletComponent } from './leaflet/leaflet.component';
import { LeafletModule } from '#asymmetrik/ngx-leaflet';
import { NgModule } from '#angular/core';
#NgModule({
declarations: [
AppComponent,
// LeafletComponent
],
imports: [
BrowserModule.withServerTransition({appId: 'my-app'}),
LeafletModule.forRoot()
],
providers: [
// BrowserModuleLoaderService
],
bootstrap: [AppComponent]
})
export class AppModule { }
./src/app/app.server.module.ts:
import {AppComponent} from './app.component';
import {AppModule} from './app.module';
import {ModuleMapLoaderModule} from '#nguniversal/module-map-ngfactory-loader';
import {NgModule} from '#angular/core';
import {ServerModule} from '#angular/platform-server';
#NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
How do I handle this nxg-leaflet module import?
Solved this issue by using Mock Browser.
server.ts:
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();
global['navigator'] = mock.getNavigator();
Fixed by (global as any).navigator = win.navigator;, much more elegant, definitely native, and without relying on outdated Mock Browser.
I too was receiving this error with Angular Universal. Using the Mock Browser from the solution above did fix this error for me, but it also started a new Warning related to CommonJS. Rather than digging into that issue, I realized I was already using Domino in my server.ts file, so I could easily set the navigator with Domino. This is what worked best for me:
npm install domino
server.ts:
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs
.readFileSync(path.join('dist/<your-app-name>/browser', 'index.html')) //<--- REPLACE WITH YOUR APP NAME
.toString();
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;
global['navigator'] = window.navigator;
I'm a beginner and I am working on a project using Angular 4.3.2, the architecture was built with JHipster.
I found the test folder with sample tests. They were successfully run before my code. I'm trying to write unit tests for components I've created, but when I execute the tests, I've this error :
Error: No provider for JhiParseLinks! in spec/entry.ts (line 2923)
_throwOrNull#webpack:///node_modules/#angular/core/#angular/core.es5.js:2649:0 <- spec/entry.ts:2923:45
_getByKeyDefault#webpack:///node_modules/#angular/core/#angular/core.es5.js:2688:0 <- spec/entry.ts:2962:37
_getByKey#webpack:///node_modules/#angular/core/#angular/core.es5.js:2620:0 <- spec/entry.ts:2894:41
get#webpack:///node_modules/#angular/core/#angular/core.es5.js:2489:0 <- spec/entry.ts:2763:30
resolveNgModuleDep#webpack:///node_modules/#angular/core/#angular/core.es5.js:9475:0 <- spec/entry.ts:9749:28
get#webpack:///node_modules/#angular/core/#angular/core.es5.js:10557:0 <- spec/entry.ts:10831:34
resolveDep#webpack:///node_modules/#angular/core/#angular/core.es5.js:11060:0 <- spec/entry.ts:11334:48
createClass#webpack:///node_modules/#angular/core/#angular/core.es5.js:10924:0 <- spec/entry.ts:11198:42
createDirectiveInstance#webpack:///node_modules/#angular/core/#angular/core.es5.js:10744:21 <- spec/entry.ts:110
Here is my test class which is built on the same model than other test classes generated by Jhipster :
import {async, ComponentFixture, TestBed} from '#angular/core/testing';
import {UserMgmtComponent} from '../../../../../../main/webapp/app/admin';
import {UserService} from '../../../../../../main/webapp/app/shared';
import {Observable} from 'rxjs/Rx';
import {User} from '../../../../../../main/webapp/app/shared';
import {ActivatedRoute} from "#angular/router";
import {MockActivatedRoute} from "../../../helpers/mock-route.service";
import {DatePipe} from "#angular/common";
import {JhiDataUtils, JhiEventManager} from "ng-jhipster";
describe('Component Tests', () => {
describe('UserManagementComponent', () => {
let fixture: ComponentFixture<UserMgmtComponent>;
let comp: UserMgmtComponent;
let service: UserService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [UserMgmtComponent],
providers: [
UserService,
JhiDataUtils,
DatePipe,
{
provide: ActivatedRoute,
useValue: new MockActivatedRoute({id: 123})
},
JhiEventManager
]
}).overrideTemplate(UserMgmtComponent, '')
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UserMgmtComponent);
comp = fixture.componentInstance;
service = fixture.debugElement.injector.get(UserService);
});
describe('OnInit part', () => {
it('Should call load all on init', () => {
spyOn(service, 'find').and.returnValue(Observable.of(new User(10)));
comp.ngOnInit();
expect(service.find).toHaveBeenCalledWith(123);
});
});
});
});
Here is the code of entry.ts :
import 'core-js';
import 'zone.js/dist/zone';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/proxy';
import 'zone.js/dist/jasmine-patch';
import 'rxjs';
import 'intl/locale-data/jsonp/en-US.js';
import { TestBed } from '#angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '#angular/platform-browser-dynamic/testing';
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
declare let require: any;
const testsContext: any = require.context('./', true, /\.spec/);
testsContext.keys().forEach(testsContext);
Does anyone ever had this error ?
Your TestBed.configureTestingModule() call lacks imports property in the arg object that imports module defined in test.module.ts and that declares providers for many services including JhiParseLinks.
If your app is named 'MyApp` you should add these lines:
import { MyAppTestModule } from '../../../test.module';
TestBed.configureTestingModule({
imports: [MyAppTestModule],
Hello and thank you for your time!
I am learning how to use Angular and I am interested in learning how to test its Components.
Currently I am struggling because I have done the Tour of Heroes tutorial of the Angular page and I am testing the code to understand it better.
The point is that I am testing hero-details component which code is:
import {Component, OnInit, Input} from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {Location} from '#angular/common';
import {Hero} from '../Hero';
#Component({
selector: 'app-hero-details',
templateUrl: './hero-details.component.html',
styleUrls: ['./hero-details.component.css']
})
export class HeroDetailsComponent implements OnInit {
#Input() hero: Hero;
constructor(private route: ActivatedRoute,
private myHeroService: MyHeroService,
private location: Location) {
}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.myHeroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
And my test tries to prove that getHero() is called after creating the hero-details component:
import {HeroDetailsComponent} from './hero-details.component';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {MessageService} from '../message.service';
import {Location} from '#angular/common';
import {provideLocationStrategy} from '#angular/router/src/router_module';
import {BrowserPlatformLocation} from '#angular/platform-browser/src/browser/location/browser_platform_location';
describe('heroDetails', () => {
it('should call getHero after being created', () => {
const heroDetailsComponent = new HeroDetailsComponent(new ActivatedRoute(),
new MyHeroService(new MessageService([])),
new Location(provideLocationStrategy(new BrowserPlatformLocation(['anyParameter']), '/')));
spyOn(heroDetailsComponent, 'getHero');
heroDetailsComponent.ngOnInit();
expect(heroDetailsComponent.getHero()).toHaveBeenCalled();
});
});
The difficulty I am facing is when I try to create a new Location which is a required parameter for the Hero-datail component's constructor.
The first Location's parameter is a PlatformStrategy, so then I used the provider to build it. Also, the provider needs a PlatformLocation() which looks like is abstract so then I chose the only implementation I could find which is BrowserPlatformLocation.
After all that process, the test execution says:
And the browser never ends loading the suite:
The strange thing here is that the IDE indeed finds those modules because I can navigate to them.
Also if I comment out that test, the suite works well:
Additionaly I have also read:
https://angular.io/api/common/Location
https://www.tektutorialshub.com/location-strategies-angular/
https://angular.io/tutorial/toh-pt5
How could I test it in a correct way? Where could I find more information about doing this type of tests properly? How could do this test to mock easily that Location parameter?
Thank you for reading this
Location is a built-in service, you do not need to instantiate it, just mock it:
const locationStub = {
back: jasmine.createSpy('back')
}
Then in your providers array:
providers: [ ..., {provide: Location, useValue: locationStub} ],
Then in your test just call the components goBack method, then use the Injector to get the instance of your service stub, like this:
const location = fixture.debugElement.injector.get(Location);
And then just test, that the back function has been called:
expect(location.back).toHaveBeenCalled();
This should solve the problem. This is, as far as I have seen, the best way to deal with the built-in services, you don't need to test them (Angular team did that already), just mock them and make sure they have been called correctly
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ],
providers: [{ provide: Location, useClass: SpyLocation }]
})
.compileComponents();
}));
it('should logout from application', async(() => {
const location: Location = TestBed.get(Location);
expect(location.href).toContain('blablabla url');
}));
Use SpyLocation from #angular/router/testing
If I were you, I would not bother creating tests from scratch : the CLI creates pre-made tests.
In those tests, there's a TestBed, that is used to set up a testing module for testing your component. If you used it, you would only have to import a router testing module.
This would give something like this :
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ]
})
.compileComponents();
}));
And just with that, your whole routing strategy is mocked.
Hello and thank you for your time:
I am trying to learn how to test a service which has a method to return an Observable.
I have done the Tour of heroes tutorial of the Angular page until the routing module, and I would like to unit test the code.
Here is the MyHeroService:
import {Injectable} from '#angular/core';
import {HEROES} from '../mock-heroes';
import {Hero} from '../Hero';
import {Observable} from 'rxjs/Observable';
import {of} from 'rxjs/observable/of';
import {MessageService} from '../message.service';
#Injectable()
export class MyHeroService {
getHeroes(): Observable<Hero[]> {
this.messageService.add('HeroService: fetched all your heroes');
return of(HEROES);
}
getHero(id: number): Observable<Hero> {
// Todo: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
constructor(private messageService: MessageService) {
}
}
And here is the unit test I am currently doing:
import {MyHeroService} from './my-hero.service';
import {MessageService} from '../message.service';
import {async, inject} from '#angular/core/testing';
describe('heroService', () => {
it('should return an Observable of Hero[]', async(() => {
const myHeroService = new MyHeroService(new MessageService([]));
myHeroService.getHero(1).subscribe(result => expect(result).toBeGreaterThan(0));
}));
});
The result which the test gives is:
I have also read:
Unit testing an observable in Angular 2
Unit test 'success' and 'error' observable response in component for Angular 2
Angular 2 Observable Service Karma Jasmine Unit Test not working
Could you help me please to understand why it gives 'Expected undefined to be greater than 0.'
I think that your problem is that you are not injecting your service correctly (you are importing 'inject' from '#angular/core/testing' but not using it).
The best way to test an Angular service is Angular TestBed class which:
Configures and initializes environment for unit testing and provides methods for creating components and services in unit tests.
import { TestBed, inject, async} from '#angular/core/testing';
import { HeroService } from './hero.service';
describe('Testing Angular Heroes', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
HeroService
]
}).compileComponents();
}));
describe("Testing my awesome function", () => {
it('should return true when I call getTrue', inject([HeroService], (heroService: HeroService) => {
expect(heroService.getTrue()).toBe(true)
}));
})
});
Here is a plunkr working with Jasmine and the example below:
https://embed.plnkr.co/Kgkozh7gZ9GMdwHgjcph/
Sources:
https://blog.thoughtram.io/angular/2016/11/28/testing-services-with-http-in-angular-2.html
https://angular.io/api/core/testing/TestBed