Getting undefined on service called from html - javascript

I'm currently working on an Angular project and I am creating unit testing for a component using Karma + Jasmine, so I have HTML that has a ngIf calling the API Service as:
HTML
<div class="row" *ngIf="apiService.utilsService.userBelongsTo('operations')"></div">
TS
export class CashFlowSalariesComponent implements OnInit, OnChanges {
constructor(
public apiService: ApiService,
) {}
SPECT.TS
describe('CashFlowSalariesComponent', () => {
let fixture: ComponentFixture < CashFlowSalariesComponent > ;
let mockCashFlowService;
let data;
beforeEach(async(() => {
data = [{
id: 1006,
role: "Developer",
...
}]
mockCashFlowService = jasmine.createSpyObj(['createTableData'])
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
RouterTestingModule,
FormsModule,
ReactiveFormsModule,
BrowserModule,
HttpClientTestingModule,
ToastrModule.forRoot({
positionClass: 'toast-bottom-right'
})
],
declarations: [
CashFlowSalariesComponent,
],
providers: [{
provide: ApiService,
useValue: mockCashFlowService
}, UserService, ProfileService, VettingStatusService, ApplicationRoleService,
SeniorityLevelService, PlacementStatusService, EducationLevelService, UtilsService, ShirtSizeService,
CountryService, CityService, PostalCodeService, StateService, ClientSectorService, JobService, ProfileActivityService, ProfileSalaryActivityService, ClientService, RequestTimeOffService, TimeOffTypeService, PulsecheckDetailService, PulsecheckMasterService,
PulsecheckQuestionService, ExpenseService, DepartmentService, ExchangeRateService, SkillCategoriesService, ProfileRoleService,
ToastrService
]
})
fixture = TestBed.createComponent(CashFlowSalariesComponent);
}));
it('should create', () => {
expect(CashFlowSalariesComponent).toBeTruthy();
});
it('should set salaries data correctly', () => {
mockCashFlowService.userBelongsTo = 'operations'
mockCashFlowService.createTableData.and.returnValue( of (data))
debugger;
fixture.detectChanges();
expect(fixture.componentInstance.dataHeaders.length).toBe(10);
})
As you see, I tried to set userBelongsTo as: mockCashFlowService.userBelongsTo = 'operations' but I get an error:
TypeError: Cannot set properties of undefined (setting
'userBelongsTo')
ApiService
#Injectable()
export class ApiService {
public utilsService: UtilsService;
constructor(private injector: Injector) {
this.utilsService = injector.get(UtilsService);
}
}
Utils.Service:
userBelongsTo(groupName: string) {
return this.groups.split(',').reduce((c, g) => c || g.toUpperCase() == groupName.toUpperCase(), false);
}

It is undefined because you have not defined it. You need to add it to your mock and then have it return something.
mockCashFlowService = jasmine.createSpyObj(['createTableData', 'userBelongsTo']);
https://volaresoftware.com/en/technical-posts/mocking-calls-with-jasmine

Related

TypeError karma test : subscribe is not a function

I have a karma unit test and the test fails with below error.
this.gridApi.getScaleWidth().subscribe is not a function
GridApi.ts
export class GridApi {
private scaleWidthSubject = new BehaviorSubject<{value: number}>({value: 0});
public getScaleWidth(): Observable<{value:number}> {
return this.scaleWidthSubject;
}
}
GridComponent.ts
export class GridComponent implements OnInit, OnDestroy, AfterViewInit {
private subscribeToValueChanges() {
this.scaleWidth$ = this.gridApi.getScaleWidth().subscribe( width => {
this.scaleWidth = width.value;
});
}
}
Component.spec.ts
describe('GridComponent', () => {
beforeEach(async () => {
const mockGridApiService = jasmine.createSpyObj("GridApi", {
getScaleWidth () : Observable<{value: number}> {
let scaleWidthSubject = new BehaviorSubject<{value: number}>({value: 0});
return scaleWidthSubject.asObservable();
}
});
}
await TestBed.configureTestingModule({
providers: [ { provide: GridApi, useValue: mockGridApiService} ],
imports: [
HttpClientModule
],
declarations: [ GridComponent ]
})
}
What should the mock getScaleWidth() return to pass the test. Not sure what I'm missing here.
describe('GridComponent', () => {
const mockGridService = jasmine.createSpyObj<GridApi>('GridApi', ['getScaleWidth'])
beforeEach(() => {
mockGridService.getScaleWidth.and.returnValue(of({ value: 0 }));
});
await TestBed.configureTestingModule({
providers: [ { provide: GridApi, useValue: mockGridService} ],
imports: [HttpClientModule],
declarations: [ GridComponent ]
})
it('should call getScaleWidth from service', () => {
// the component function that triggers the service call is private
// make the call from component
expect(mockGridService.getScaleWidth).toHaveBeenCalled();
mockGridService.getScaleWidth().subscribe(response => {
expect(response.value === 0)
})
})
}

Do you always need a mock for ActivatedRoute?

So I am doing some unit tests. And the following component I try to unit test:
export class EcheqDisplayComponent implements OnInit {
echeq: EcheqSubmissionApi;
orgId = 1;
constructor(
private route: ActivatedRoute
) {
this.echeq = this.route.snapshot.data['submission'];
}
ngOnInit() {
}
getAnswers(page: EcheqPageApi): any[] {
return page.elements.map(element => this.echeq.answers[element.name]);
}
And this is the unit test for it:
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
imports:[ParticipantEcheqModule,
RouterModule.forRoot([])
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
expect(component).toBeTruthy();
});
});
So I dont get errors.
But my question is. Is this the correct way to do ti?
Because is it not better to use a mock for ActivatedRoute?
An if yes, how to do it then the correct way?
Thank you
sO THIS IS the template of it:
<div class="echeq-display" *ngIf="echeq">
<header class="echeq-display-header header">
<div class="echeq-display-info">
<h1 class="heading echeq-display-heading">
{{ echeq.definition.title }}
</h1>
<div class="sub-heading echeq-display-subheading">
<span class="echeq-display-creator">
Toegekend door:
{{ echeq.assignedByProfName ? echeq.assignedByProfName : 'Het Systeem' }}
</span>
<span class="echeq-display-date">{{
echeq.definition.createdOnUtc | date: 'dd MMM'
}}</span>
</div>
</div>
<app-meta-box
[metadata]="{
numPages: echeq.definition.numPages,
vPoints: echeq.definition.awardedVPoints
}"
></app-meta-box>
</header>
<main class="echeq-display-questions body">
<app-echeq-question
*ngFor="let page of echeq.definition.pages; let i = index"
[page]="page"
[readonly]="true"
[number]="i + 1"
[answers]="getAnswers(page)"
></app-echeq-question>
</main>
</div>
and the unit test like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
and this is the app-meta-box component:
export class MetaData {
numPages: number;
vPoints: number;
}
#Component({
selector: 'app-meta-box',
templateUrl: './meta-box.component.html',
styleUrls: ['./meta-box.component.scss']
})
export class MetaBoxComponent implements OnInit {
#Input() metadata: MetaData;
constructor() {}
ngOnInit() {}
}
You can simply provide mock ActivatedRoute object with necessary variable set in "providers" property when "TestBed.configureTestingModule({...})" is called to configure testing module.
class MockActivatedRoute {
public snapshot = {
data: {
submission: {
answers: {}
}
}
};
}
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
imports:[
ParticipantEcheqModule,
// RouterModule.forRoot([]) // Not required after mocking the "ActivatedRoute"
]
})
.compileComponents();
This way you can test your component for different values in activatedRoute.

How to inject Input() in unit testing?

So i try to do some unit testing on a component. But I have some problems with Input() parameter. And then especially a component in the component
SO I have this component:
export class EcheqDisplayComponent implements OnInit {
echeq: EcheqSubmissionApi;
constructor(
private route: ActivatedRoute
) {
this.echeq = this.route.snapshot.data['submission'];
}
ngOnInit() {
}
getAnswers(page: EcheqPageApi): any[] {
return page.elements.map(element => this.echeq.answers[element.name]);
}
}
and the template:
<div class="echeq-display" *ngIf="echeq">
<header class="echeq-display-header header">
<div class="echeq-display-info">
<h1 class="heading echeq-display-heading">
{{ echeq.definition.title }}
</h1>
<div class="sub-heading echeq-display-subheading">
<span class="echeq-display-creator">
Toegekend door:
{{ echeq.assignedByProfName ? echeq.assignedByProfName : 'Het Systeem' }}
</span>
<span class="echeq-display-date">{{
echeq.definition.createdOnUtc | date: 'dd MMM'
}}</span>
</div>
</div>
<app-meta-box
[metadata]="{
numPages: echeq.definition.numPages,
vPoints: echeq.definition.awardedVPoints
}"
></app-meta-box>
</header>
<main class="echeq-display-questions body">
<app-echeq-question
*ngFor="let page of echeq.definition.pages; let i = index"
[page]="page"
[readonly]="true"
[number]="i + 1"
[answers]="getAnswers(page)"
></app-echeq-question>
</main>
</div>
and the unit test:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { EcheqDisplayComponent } from './echeq-display.component';
import { ParticipantEcheqModule } from '../../participant-echeq.module';
import { RouterModule, ActivatedRoute } from '#angular/router';
import { MockActivatedRoute } from 'src/app/shared/mocks/MockActivatedRoute';
import { MetaData } from '../meta-box/meta-box.component';
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
const metaData: MetaData = new MetaData();
// const metaDataInfo = fixture.debugElement.componentInstance;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
metaData.numPages = 20;
expect(component).toBeTruthy();
});
});
But it stay saying:
TypeError: Cannot read property 'numPages' of undefined.
So what I have to change that it is working?
Thank you
So the MetaData is from the app-meta-box component.it looks like this:
export class MetaData {
numPages: number;
vPoints: number;
}
#Component({
selector: 'app-meta-box',
templateUrl: './meta-box.component.html',
styleUrls: ['./meta-box.component.scss']
})
export class MetaBoxComponent implements OnInit {
#Input() metadata: MetaData;
constructor() {}
ngOnInit() {}
}
This is the mock class:
export class MockActivatedRoute {
public snapshot = {
data: {
submission: {
answers: {}
}
}
};
}
I have it now like this:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { EcheqDisplayComponent } from './echeq-display.component';
import { ParticipantEcheqModule } from '../../participant-echeq.module';
import { RouterModule, ActivatedRoute } from '#angular/router';
import { MockActivatedRoute } from 'src/app/shared/mocks/MockActivatedRoute';
import { MetaData } from '../meta-box/meta-box.component';
describe('EcheqDisplayComponent', () => {
let component: EcheqDisplayComponent;
let fixture: ComponentFixture<EcheqDisplayComponent>;
const metaData: MetaData = new MetaData();
// const metaDataInfo = fixture.debugElement.componentInstance;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ],
providers: [
{ provide: ActivatedRoute, useValue: new MockActivatedRoute().withData({submission:{ answers:{} } }) }
],
imports:[
ParticipantEcheqModule
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EcheqDisplayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
fit('should create component', () => {
component.echeq = {
definition: {
title: 'test title',
awardedVPoints: 0,
numPages: 9
}
}
expect(component).toBeTruthy();
});
});
But then I get this error:
Property 'answers' is missing in type '{ definition: { title: string; awardedVPoints: number; numPages: number; }; }' but required in type 'EcheqSubmissionApi'.ts(2741)
echeqSubmissionApi.ts(59, 5): 'answers' is declared here.
I have it now like this:
fit('should create component', () => {
component.echeq = {
definition: {
title: 'test title',
awardedVPoints: 0,
numPages:9
}
} as EcheqSubmissionApi;
expect(component).toBeTruthy();
});
export interface EcheqSubmissionApi {
/**
* Primary key of this submission (server set).
*/
id?: string;
definition?: EcheqDefinitionApi;
/**
* Id of the prof who assigned this eCheq (server set).
*/
assignedByProfId?: string;
/**
* Name of the prof who assigned this eCheq (server set). Only set on get operations. May be null if assigned by the system.
*/
assignedByProfName?: string;
/**
* Id of the organisation who assigned this eCheq (server set).
*/
assignedByOrgId?: number;
/**
* Participant ID of the patient this eCheq is assigned to (server set).
*/
assignedToId?: string;
/**
* When this submission was assigned (UTC, server set).
*/
assignedOnUtc?: Date;
/**
* If set until when the eCheq can be submitted.
*/
validUntilUtc?: Date;
/**
* Whether the eCheq has been started and whether it has been submitted (server set).
*/
status?: EcheqSubmissionApi.StatusEnum;
/**
* When this submission was completed (UTC, server set).
*/
submittedOnUtc?: Date;
/**
* Answers of form. In the form of a json object with name and value of the questions { \"nameOfQuestion\" : \"valueOfQuestion\" }
*/
answers: object;
/**
* initialValues of form. In the form of a json object with name and value of variables { \"nameOfQuestion\" : \"valueOfQuestion\" }
*/
initialValues?: object;
/**
* The page the participant is currently on
*/
currentPage?: number;
/**
* The progress of the echeq in percentage
*/
progress?: number;
}
export namespace EcheqSubmissionApi {
export type StatusEnum = 'New' | 'Active' | 'Submitted';
export const StatusEnum = {
New: 'New' as StatusEnum,
Active: 'Active' as StatusEnum,
Submitted: 'Submitted' as StatusEnum
};
}
But if I run the unit test, I still get this error:
TypeError: Cannot read property 'numPages' of undefined
To answer your question:
component.metadata = /// whatever you want this to be
You have your error with numPages: echeq.definition.numPages. Endeed, echeq is also undefined.
You can try :
component.echeq = {
definition: {
numPages: 9
}
}
Or better way would be to returns this value from this.route.snapshot.data['submission']; so from MockActivatedRoute
UPDATE:
And update MockActivatedRoute to allows dynamic parameters:
export class MockActivatedRoute {
snapshot = {
data: {}
};
constructor(){}
withData(data:any): MockActivatedRoute {
this.snapchot.data = data;
return this;
}
}
So now in your test, you can use it :
{ provide: ActivatedRoute, useValue: new MockActivatedRoute().withData({submission:{ answers:{} } }) }
No problem. It was an easy:
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
],
imports:[
ParticipantEcheqModule,
RouterTestingModule
]
})
.compileComponents();
}));

Angular: trying to add recaptcha. Error => No value accessor for form control with name: 'captcha'

So I followed a tutorial on how to add recaptcha to a form to prevent spam. I followed all the steps, but I still get the following error:
error inside log Error: No value accessor for form control with name: 'captcha'
at _throwError (forms.js:2139)
at setUpControl (forms.js:2008)
at FormGroupDirective.addControl (forms.js:5144)
at FormControlName._setUpControl (forms.js:5743)
at FormControlName.ngOnChanges (forms.js:5684)
at checkAndUpdateDirectiveInline (core.js:11642)
at checkAndUpdateNodeInline (core.js:13252)
I have a reactive signup form where I have the following element that's supposed to be the captcha and a formControl. BUt unfortunately I see nothing.
<div nbRecaptcha key="6LfNfHYUAAAAAFaa7dVi2au1tnfod-2eR3Rb2enM" formControlName="captcha"></div>
This is the nbRecaptcha directive:
declare const grecaptcha: any;
declare global {
interface Window {
grecaptcha: any;
reCaptchaLoad: () => void
}
}
#Directive({
selector: '[nbRecaptcha]',
providers: [ReCaptchaAsyncValidator]
})
export class RecaptchaDirective implements OnInit, ControlValueAccessor, AfterViewInit {
#Input() key: string;
#Input() config: ReCaptchaConfig = {};
#Input() lang: string;
private control: FormControl;
private onChange: ( value: string ) => void;
private onTouched: ( value: string ) => void;
private widgetId: number;
constructor(
private element: ElementRef,
private ngZone: NgZone,
private injector: Injector,
private reCaptchaAsyncValidator: ReCaptchaAsyncValidator){}
ngOnInit() {
this.registerReCaptchaCallback();
this.addScript();
}
ngAfterViewInit() {
this.control = this.injector.get(NgControl).control;
this.setValidator();
}
addScript() {
let script = document.createElement('script');
const lang = this.lang ? '&hl=' + this.lang : '';
script.src = `https://www.google.com/recaptcha/api.js?onload=reCaptchaLoad&render=explicit${lang}`;
script.async = true;
script.defer = true;
document.body.appendChild(script);
}
// We need to notify the formControl that it’s valid if we get the token
// from the onSuccess function or that it’s invalid if the onExpired function is called.
onExpired() {
this.ngZone.run(() => {
this.onChange(null);
this.onTouched(null);
});
}
onSuccess( token: string ) {
this.ngZone.run(() => {
this.verifyToken(token);
this.onChange(token);
this.onTouched(token);
});
}
// these are the three methods that controlValueAccessor requires
writeValue( obj: any ): void {
}
registerOnChange( fn: any ): void {
this.onChange = fn;
}
registerOnTouched( fn: any ): void {
this.onTouched = fn;
}
private setValidator() {
this.control.setValidators(Validators.required);
this.control.updateValueAndValidity();
}
registerReCaptchaCallback() {
window.reCaptchaLoad = () => {
const config = {
...this.config,
'sitekey': this.key,
'callback': this.onSuccess.bind(this),
'expired-callback': this.onExpired.bind(this)
};
this.widgetId = this.render(this.element.nativeElement, config);
};
}
private render( element: HTMLElement, config ): number {
return grecaptcha.render(element, config);
}
verifyToken( token : string ) {
this.control.setAsyncValidators(this.reCaptchaAsyncValidator.validateToken(token))
this.control.updateValueAndValidity();
}
}
And the signup component form:
this.mySignupForm = new FormGroup({
captcha: new FormControl()
});
Can Anyone see what I did wrong?
The error No value accessor for form control with name: 'captcha'. This can be resolved by adding the directive/control to list of all available NG_VALUE_ACCESSORs using forwardRef. You are effectively extending the multi-provider for NG_VALUE_ACCESSOR so that this directive can access ControlValueAccessor.
#Directive({
selector: '[nbRecaptcha]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RecaptchaDirective),
multi: true
},
ReCaptchaAsyncValidator
]
})
Here is a mostly working example with that specific error being resolved. I had to remove some of the custom classes/configurations you had in your example as I wasn't aware of their exact implementations.
I could not find official documentation for creating custom controls, but there are a few solid articles out there discussing creating custom controls that reference this necessary registration.
Hopefully that helps!
Sometimes we get this kind of error when import it in app.module instead of the own module where we need to use . for example
///app.module.ts
import { RecaptchaModule, RecaptchaFormsModule } from "ng-recaptcha";
imports: [
BrowserModule,
RecaptchaModule,
RecaptchaFormsModule
]
move it to --->
///x.module.ts
import { RecaptchaModule, RecaptchaFormsModule } from "ng-recaptcha";
#NgModule({
imports: [
FormsModule,
RecaptchaModule,
RecaptchaFormsModule,
],
providers:[
{
provide: RECAPTCHA_SETTINGS,
useValue: { siteKey: environment.recaptcha.siteKey } as RecaptchaSettings,
},
]

Testing Kendo Grid in Angular

I'm trying to get my Karma tests working with the kendo grid in a brand new Angular project. The specs for this component look like
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RateTableProviderService } from "../_services/rate-table-provider.service";
import { RateTableComponent } from './rate-table.component';
import { Observable } from 'rxjs/Rx';
import { Response, ResponseOptions } from "#angular/http";
import { IRateTableListViewModel } from "./models/IRateTableListViewModel";
import { GridModule } from '#progress/kendo-angular-grid';
describe('RateTableComponent', () => {
let component: RateTableComponent;
let fixture: ComponentFixture<RateTableComponent>;
let observableSource = [
{
id: "3482cd2f-16f8-4d62-8d5d-d761de35e737",
name: "Rate table 1",
enabled: false,
effectiveDate: new Date(),
creditTierSetName: "",
loanProducts: ""
},
{
id: "3482cd2f-16f8-4d62-8d5d-d761de35e739",
name: "Another rate table",
enabled: false,
effectiveDate: new Date(),
creditTierSetName: "",
loanProducts: ""
}
];
class MockRateTableProviderService extends RateTableProviderService {
constructor() {
super(null);
}
getRateTableData(): Observable<Array<IRateTableListViewModel>> {
return Observable.from([observableSource]);
}
}
beforeEach(async(() => {
let mockRateTableProviderService = new MockRateTableProviderService();
TestBed.configureTestingModule({
declarations: [RateTableComponent],
providers: [
{ provide: RateTableProviderService, useValue: mockRateTableProviderService }
],
imports: [GridModule]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RateTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
The tests run fine against chrome but in PhantomJS the tests hang.
PhantomJS 2.1.1 (Windows 8 0.0.0) RateTableComponent should be created FAILED
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Everything works fine when running the tests against Chrome. I suspect that I'm not managing the asynchronous import when setting up the test bed correctly. If I pull out the kendo completely then the tests complete. I tried passing in a done call as part of the beforeEach but that also didn't work
beforeEach(async((done) => {
let mockRateTableProviderService = new MockRateTableProviderService();
TestBed.configureTestingModule({
declarations: [RateTableComponent],
providers: [
{ provide: RateTableProviderService, useValue: mockRateTableProviderService }
],
imports: [GridModule]
})
.compileComponents().then(done);
}));
Have you tried defining your fixture and component in the resolve of .compileComponents() as this will then be executed async as well as the setup of your testbed?
`.compileComponents.()
.then(() => {
fixture = TestBed.createComponent(RateTableComponent);
component = fixture.componentInstance;
});`
Try adding a second before each like so:
beforeEach(async(() => {
let mockRateTableProviderService = new MockRateTableProviderService();
TestBed.configureTestingModule({
declarations: [RateTableComponent],
providers: [
{ provide: RateTableProviderService, useValue: mockRateTableProviderService }
],
imports: [GridModule]
})
.compileComponents().then(done);
}));
beforeEach(() => {
done();
}));
not as elegant as my main man simons answer above but should do the trick

Categories