I have created a component that opens my custom type dialog, I just want to create Jasmine unit test cases for this method.
export class OpenPopUpComponent implements OnInit {
constructor(public dialog:NewCustomDialog) {}
ngOnInit() {
}
openModel(){
this.dialog.open(NewComponent,<NewCustomDialogConfig>{
size: 'double',
data: {
title: 'New Dialog'
}
});
}
}
You will not test the dialog itself. What you need to do is to mock the NewCustomDialog and provide it as injected.
In your spec.ts
beforeEach(() => {
const spy = jasmine.createSpyObj('NewCustomDialog', ['open']);
TestBed.configureTestingModule({
// Provide (spy) dependency
providers: [
{ provide: NewCustomDialog, useValue: {newCustomDialogSpy} }
]
});
// Inject both the service-to-test and its (spy) dependency
masterService = TestBed.get(MasterService);
valueServiceSpy = TestBed.get(ValueService);
});
Then you can check that the spy has been called with parameters (the ones you expect).
The intension of the unit test is to test the feature of component itself and not to start testing the features which is outside the scope of component which is to be tested. So,
you do not need to test dialog.open as this should be tested in unit test of NewCustomDialog itself.
start by creating a Stub which you can use as a placeholder for NewCustomDialog, such as
export class NewCustomDialogStub{
open(){ return null; }
close(){ return null; }
// and similar dummy methods which is required by "OpenPopUpComponent"
}
Inject this stub as useClass in providers as below:
export class NewCustomDialogStub{
open(){ return null; }
close(){ return null; }
// and similar dummy methods which is required by "OpenPopUpComponent"
}
describe('OpenPopUpComponent', () => {
let component: OpenPopUpComponent;
let fixture: ComponentFixture<OpenPopUpComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
declaration: [OpenPopUpComponent],
providers: [
{ provide: NewCustomDialog, useClass: NewCustomDialogStub }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(OpenPopUpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be defined',()=>{
expect(component).toBeDefined();
})
it('should call "open" method of dialog on calling openModel() ',()=>{
spyon(component.dialog,'open').and.callThrough();
component.openModel();
expect(component.dialog.open).toHaveBeenCalled();
})
})
This is very basic testing but if you want to know more about writing tests , you can refer to this series of articles where I have covered almost all basic testing scenarios . Check the bottom of article for all links. The one which I used here is this one
Related
I create belowed decorator to get current logged in to the system user,
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
but I do not want to use this because i need to use in any of my controller
which is a bit troublesome for me because some functions are optional, i.e. both for the logged in user and the non logged in user,
so, how can I get current logged in user in my service in functions when i want to get current user instead of all via decorator in controller?
thanks for any help
You'd have to make a custom provider and inject the request into it. Something like this
{
provider: 'CURRENT_USER',
inject: [REQUEST],
useFactory: (req: Request) => {
return req.user;
},
scope: Scope.REQUEST,
}
(REQUEST is injected from #nestjs/core)
Then the user can be injected into the service with #Inject('CURRENT_USER'). Keep in mind, this will make the service REQUEST scoped, and by scope hierarchy it will make whatever you inject the service into REQUEST scoped.
Edit 2/15/21
An example of this module could look something like this:
#Module({
providers: [{
provider: 'CURRENT_USER',
inject: [REQUEST],
useFactory: (req: Request) => {
return req.user;
},
scope: Scope.REQUEST,
}],
exports: ['CURRENT_USER'],
})
export class CurrentUserModule {}
And now in whatever module that has the service that needs the current user you do
#Module({
imports: [CurrentUserModule],
providers: [ServiceThatNeedsUser],
})
export class ModuleThatNeedsUser {}
and in the service:
#Injectable()
export class ServiceThatNeedsUser {
constructor(#Inject('CURRENT_USER') private readonly user: UserType) {}
// rest of class implementation
}
I have an Angular 4 component that listens to a dblclick event. This works fine in the browser:
acticle.component.html
<div (dblclick)="openArticle()">
<!-- stuff -->
</div>
acticle.component.ts
#Component({
selector : 'app-article',
templateUrl: 'article.component.html',
styleUrls : ['article.component.scss'],
})
export class ArticleComponent implements OnInit {
// stuff
openArticle(): void {
// simplified code
this.openArticleInCs();
}
}
Now I am trying to test if the double click properly executes what happens inside openArticle(), like so:
describe('Component: ArticleComponent', () => {
// some variable declarations here
beforeEach(() => {
// some variable definitions here
TestBed.configureTestingModule({
declarations: [/* some declarations here */],
imports: [/* some modules here */],
providers: [/* some providers here */]
});
fixture = TestBed.createComponent(ArticleFactoryComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
de = fixture.debugElement.query(By.directive(ArticleComponent));
el = de.nativeElement;
articleComponent = de.injector.get(ArticleComponent);
});
it('should open an article in CS on double click', () => {
articleComponent.openArticleInCue = jasmine.createSpy('openArticleInCue').and.returnValue(undefined);
articleComponent.openArticleInCs = jasmine.createSpy('openArticleInCs').and.returnValue(undefined);
de.triggerEventHandler("dblclick", new MouseEvent("dblclick"));
fixture.detectChanges();
expect(articleComponent.openArticleInCs).toHaveBeenCalled();
});
});
But the tests fail at
expect(articleComponent.openArticleInCs).toHaveBeenCalled();
Using the debugger I can see that triggerEventHandler works and inside that function Angular iterates though the defined event handlers for this component. However, the event handlers are empty! So it seems to me Angular's test suite does not understand that I have a dblclick handler there (if i am not mistaken on my presumption that is).
Angular 4 supports below syntax
var HelloComponent = ng.core
Component({
selector: 'hello-cmp',
template: 'Hello World!',
viewProviders: [Service]
.Class({
constructor: [Service, function (service) {
},`
});
In Angular 5 , Class is missing anyone can provide Angular 5 with ES5 syntax currently
i am not able to switch ES6 so please avoid that suggestion.
if switching to ES6 is the only way then i will stick to angular 4 as of now
You need to use static properties annotations and parameters on your component class function something like this:
function Service() {}
function AppComponent(service) {
console.log(service);
}
AppComponent.prototype.ngOnInit = function() {
console.log('test ngOnInit');
};
AppComponent.annotations = [
new Component({
selector: 'my-app',
template: '<h1>Example of Angular 5.0.5 in ES5</h1>',
viewProviders: [Service]
})
];
AppComponent.parameters = [ Service ];
Plunker Example
I am using angular-cli testing framework.
inside my component , I have used 'ng2-slim-loading-bar' node module.
submit(){
this._slimLoadingBarService.start(() => {
});
//method operations
}
Now when I am testing this component, I have applied spyOn this service as :
beforeEach(() => {
let slimLoadingBarService=new SlimLoadingBarService();
demoComponent = new DemoComponent(slimLoadingBarService);
TestBed.configureTestingModule({
declarations: [
DemoComponent
],
providers: [
{ provide: SlimLoadingBarService, useClass: SlimLoadingBarService}
],
imports: [
SharedModule
]
});
});
it('should pass data to servie', () => {
spyOn(slimLoadingBarService,'start').and.callThrough();
//testing code,if I remove the above service from my component, test runs fine
});
but its not working.
It throws below error:
spyOn could not find an object to spy upon for start()
Declaring slimLoadingBarService with let, you are constraining its scope to the beforeEach callback scope. Declare it with var, or better, declare it after the proper describe() block and set its content within beforeEach callback function:
describe("some describe statement" , function(){
let slimLoadingBarService = null;
beforeEach( () => {
slimLoadingBarService=new SlimLoadingBarService();
});
it('should pass data to service', () => {
spyOn(slimLoadingBarService,'start').and.callThrough();
//testing code,if I remove the above service from my component, test runs fine
});
});
it's due to non declaration in beforeEach
updated syntax after angular 10
beforeEach(() => {
slimLoadingBarService = TestBed.inject(SlimLoadingBarService);
});
before angular 10
beforeEach(() => {
slimLoadingBarService = TestBed.get(SlimLoadingBarService);
});
I am using Google API Javascript library in my Angular 2 application. I have created a service which is injected in components. Here is the code:
import { Injectable } from '#angular/core';
const url = 'https://apis.google.com/js/client.js?onload=__onGoogleLoaded';
#Injectable()
export class GoogleAPIService {
public client: any;
public calculatorService: any;
public actionService: any;
loadAPI: Promise<any>
constructor(){
this.loadAPI = new Promise((resolve) => {
window['__onGoogleLoaded'] = (ev) => {
console.log('gapi loaded');
resolve(window['gapi']);
this.client = window['gapi'].client;
this.loadEndPoints('{Endpoint URL}/_ah/api');
}
this.loadScript();
});
}
doSomethingGoogley(){
return this.loadAPI.then((gapi) => {
console.log(gapi);
});
}
loadScript(){
console.log('loading..')
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);
}
loadEndPoints(apiRoot) {
// Loads the OAuth and calculatorendpoint APIs asynchronously, and triggers login
// when they have completed.
var apisToLoad;
var callback = function() {
console.log('API Loaded '+apisToLoad);
if (--apisToLoad == 0) {
//this.endpoint1= this.client.endpoint1; //Doesn't Work
//this.endpoint2= this.client.endpoint2;
}
}
apisToLoad = 3; // must match number of calls to gapi.client.load()
this.client.load('oauth2', 'v2', callback);
this.client.load('endpoint1', 'v1', callback, apiRoot);
this.client.load('endpoint2','v1',callback,apiRoot);
}
}
I have three questions:
How do I get the endpoints gapi.client.endpoint1 as a public variable in the service?
How do I call the methods in the api? In javascript, u can jst call gapi.client.endpoint1.method().execute()
How do I make this service singleton?
Any help is appreciated.
EDIT:
Here is the working version of the service. I use it as provider in my Root module. thus, its available as singleton throughout the application.
import { Injectable } from '#angular/core';
const url = 'https://apis.google.com/js/client.js?onload=__onGoogleLoaded';
const gapiOnLoaded = '__onGoogleLoaded';
const clientName = 'gapi';
const endpointhost = '[HTTPS URL FOR ENDPOINTS]';
const apiEndPoint = endpointhost + '/_ah/api';
#Injectable()
export class GoogleAPIService {
private gapi: any;
private loadAPI: Promise<any>;
constructor() {
this.loadAPI = new Promise((resolve) => {
window[gapiOnLoaded] = (ev) => {
this.gapi = window[clientName];
// Loads the OAuth and other APIs asynchronously, and triggers login
// when they have completed.
let apisToLoad;
let callback = function() {
if (--apisToLoad === 0) {
resolve(window[clientName]);
}
};
apisToLoad = 3; // must match number of calls to gapi.client.load()
this.gapi.load('client:auth2', callback);
this.gapi.client.load('[ENDPOINT_1_NAME]', 'v1', callback, apiEndPoint);
this.gapi.client.load('[ENDPOINT_2_NAME]', 'v1', callback, apiEndPoint);
};
this.loadScript();
});
}
public GetClient(): any {
return this.loadAPI.then((res) => {
return this.gapi;
});
}
private loadScript() {
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
Inject this service in other services. I created a service for each of the endpoints.
#Injectable()
export class Endpoint1Service {
private gapi: any;
constructor(private googleApiService: GoogleAPIService) {
}
public isLoad() {
return this.googleApiService.GetClient().then((gapi) => {
this.gapi = gapi;
return true;
});
}
public action(data: DataType){
this.gapi.client.endpointname.apimethod(data).execute();
}
}
Services are singletons by default. You should provide it in your AppModule, and then it will be available to all of your components. Just make sure to include it in your component constructors.
import {NgModule} from '#angular/core';
import {BrowserModule} from '#angular/platform-browser';
import {HttpModule} from '#angular/http';
import { AppComponent } from './app.component';
import { routing } from './app.routing';
import { GoogleService } from './google.service'; // important
#NgModule({
imports: [
BrowserModule,
HttpModule,
routing,
],
declarations: [ AppComponent],
providers: [ GoogleService ], // important
bootstrap: [ AppComponent],
})
export class AppModule {
}
To make an endpoint available outside of your service, you can use the public keyword in front of the function that calls the endpoint. To call the endpoint in angular2, you can use the built-in http service from #angular/http. Here's an example service (only using HTTP GET) that will return an Observable for the endpoints you call.
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs';
#Injectable()
export class GoogleService {
constructor(private http: Http) { }
public endpoint1(): Observable<any> {
return this.http.get("http://endpoint.one.com");
}
public endpoint2(): Observable<any> {
return this.http.get("http://endpoint.two.com");
}
}
You can then use the service like this in your component.
import { Component, OnInit } from '#angular/core';
import { GoogleService } from './google.service'; // important
#Component({
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
constructor(private googleService: GoogleService) { }
ngOnInit() {
this.googleService.endpoint1().subscribe(callback1, handleError);
this.googleService.endpoint2().subscribe(callback2, handleError);
}
callback1(data){
// do something with the data from ONE
}
callback2(data){
// do something with the data from TWO
}
handleError(error: any){
console.error(error);
}
}
I recommend reading up a bit on using Observables in this post from Angular University.
I don't think modifying the DOM to load gapi is particularly good practice. It's probably better to use gapi and gapi.auth TypeScript definitions by installing them with NPM.
I've posted instructions on how to do this in my answer to Import gapi.auth2 in angular 2 typescript.