Cannot read property 'length' of null + "angular"? - javascript

I am trying to count initial li and after fetching data from server (using unit test case), but I am getting Cannot read property 'length' of null
My code:
import { ComponentFixture, TestBed, async, getTestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { MockAppService } from './mock.service'
import { DebugElement } from '#angular/core';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>,
component: AppComponent,
service: AppService,
debugEl:DebugElement,
el:HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [
AppComponent
],
providers: [{ provide: AppService, useClass: MockAppService }]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
service = TestBed.get(AppService);
el = fixture.nativeElement;
spyOn(service, 'getData').and.callThrough();
}));
afterEach(() => {
//httpMock.verify();
});
describe('AppComponent onit test', () => {
it('intial list item', async(() => {
debugEl = fixture.debugElement.query(By.css('li'));
expect(fixture.nativeElement.querySelector('li').length()).toBe(0);
}));
it('should called appService getData method', async(() => {
fixture.detectChanges();
expect(service.getData).toHaveBeenCalled();
}));
it('should return an Observable<User[]>', () => {
const dummyUsers = [{
userId: 10,
id: 10,
title: "post",
body: "post"
}];
service.getData().subscribe(users => {
console.log(users)
expect(users.length).toBe(1);
expect(users).toEqual(dummyUsers);
expect(fixture.nativeElement.querySelector('li').length()).toBe(1);
});
});
})
});
code link
https://stackblitz.com/edit/angular-testing-w9towo?file=app%2Fapp.component.spec.ts

query does not return an array, try to use queryAll to return an array instead of query
here is the solution:
it('intial list item', async(() => {
let liCount= fixture.debugElement.queryAll(By.css('li'));
expect(liCount.length).toBe(0);
}));

When you look at this documentation: https://angular.io/api/core/DebugElement
You will find that DebugElement.query method returns DebugElement
and the method DebugElement.queryAll method returns DebugElement[]
so DebugElement.queryAll will return an array of DebugElement
and in this case, since it's an array you can then apply length property to it.
So you have to use DebugElement.queryAll in order to test the length of the resulting array.

Related

NullInjectorError while testing

I have the following problem while running ng test in Angular 12:
NullInjectorError: R3InjectorError(DynamicTestModule)[BaseURL ->
BaseURL]: NullInjectorError: No provider for BaseURL! error
properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'BaseURL',
'BaseURL' ] }) NullInjectorError:
R3InjectorError(DynamicTestModule)[BaseURL -> BaseURL]:
NullInjectorError: No provider for BaseURL!
at NullInjector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11101:1)
at R3Injector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11268:1)
at R3Injector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11268:1)
at NgModuleRef$1.get (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:25332:1)
at Object.get (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:25046:1)
at lookupTokenUsingModuleInjector (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3342:1)
at getOrCreateInjectable (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3454:1)
at ɵɵdirectiveInject (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:14737:1)
at NodeInjectorFactory.MenuComponent_Factory [as factory] (ng:///MenuComponent/ɵfac.js:5:7)
at getNodeInjectable (http://localhost:9876/karma_webpack/webpack:/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3549:1)
Error: Expected undefined to be truthy.
at
at UserContext. (http://localhost:9876/karma_webpack/webpack:/src/app/menu/menu.component.spec.ts:46:23)
at ZoneDelegate.invoke (http://localhost:9876/karma_webpack/webpack:/node_modules/zone.js/fesm2015/zone.js:372:1)
at ProxyZoneSpec.onInvoke (http://localhost:9876/karma_webpack/webpack:/node_modules/zone.js/fesm2015/zone-testing.js:287:1)
The code for the spec.ts is:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { MenuComponent } from './menu.component';
import { DishService } from '../services/dish.service';
describe('MenuComponent', () => {
let component: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
const mockDishService = {
getDishes: () => {
return {
id: '000',
name: 'nnnnn'
}
}
}
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [],
declarations: [ MenuComponent ],
providers: [
{ provide: DishService, useValue: mockDishService },
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
The code of the component is:
import { Component, OnInit, Inject } from '#angular/core';
import { Dish } from '../shared/dish';
import { DishService } from '../services/dish.service';
import { flyInOut, expand } from '../animations/app.animation';
#Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
// tslint:disable-next-line:use-host-property-decorator
host: {
'[#flyInOut]': 'true',
'style': 'display: block;'
},
animations: [
flyInOut(),
expand()
]
})
export class MenuComponent implements OnInit {
dishes!: Dish[];
errMess: string;
constructor(private dishService: DishService,
#Inject ('BaseURL') public baseURL) { }
ngOnInit(): void {
this.dishService.getDishes().subscribe((dishes => this.dishes = dishes), errMess => this.errMess = <any>errMess);
}
}
I'm using the baseURL to obtain the information for a db.json file while running json-server --watch db.json to simulate that I get the information from the server, so I have a shared .ts file named baseurl.ts with the following code:
export const baseURL = 'http://localhost:3000';
And in the app.module.ts I import the const
import { baseURL } from './shared/baseurl';
and I add it as a provider in the same app.module.ts file:
providers: [
{ provide: 'BaseURL', useValue: baseURL }
],
I will appreciate someone is help on this error
Issue 1: NullInjectorError
This error message shows as your DishComponent requires BaseURL to be injected [#Inject ('BaseURL')].
NullInjectorError: R3InjectorError(DynamicTestModule)[BaseURL -> BaseURL]: NullInjectorError: No provider for BaseURL!
Solution for Issue 1
Ensure that you add BaseURL in providers section for unit testing.
const baseUrl = "your Base URL";
beforeEach(async () => {
await TestBed.configureTestingModule({
...
providers: [
{ provide: DishService, useValue: mockDishService },
{ provide: 'BaseURL', useValue: baseUrl },
]
})
.compileComponents();
});
Issue 2: Return wrong value for mockDishService.getDishes()
From MenuComponent, it is expected that the dishService.getDishes() return the value of Observable<Menu[]> or Observable<any[]> type.
dishes!: Dish[];
this.dishService.getDishes().subscribe((dishes => this.dishes = dishes), errMess => this.errMess = <any>errMess);
While the mockDishService.getDishes() returns the value of any or Menu type, which the data returned was unmatched.
const mockDishService = {
getDishes: () => {
return {
id: '000',
name: 'nnnnn',
};
},
};
Hence you will get this error message as below:
TypeError: this.dishService.getDishes(...).subscribe is not a function
Solution for Issue 2:
Ensure that the mockDishService.getDishes() returns value of Observable<Menu[]> or Observable<any[]> type to match with real dishService.getDishes().
import { of } from 'rxjs';
const mockDishService = {
getDishes: () => {
return of([
{
id: '000',
name: 'nnnnn',
},
]);
},
};
Sample Solution on StackBlitz

how to test a function with condition in ngOnInit

I want to test a function with condition that comes from a service in ngOnInit. I tried many way but no success. i have all kinds of mistakes.
my component
export class MainSectionComponent implements OnInit {
propertiesFrDb: PropertyPost[];
constructor(
private getPropertiesFrDbService: GetPropertiesFrDbService,
private propertyWarehouseService: PropertyWarehouseService,
private router: Router,
config: NgbCarouselConfig,
private userService: UserService,
private sharedFunctionService: SharedFunctionService,
private returnResponseAfterUserLoginService: ReturnResponseAfterUserLoginService,
private localStorageService: LocalStorageServiceService,
private dialogLoginService: DialogLoginService,
#Inject(PLATFORM_ID) private platformId: Object
) {
// this.isBrowser = isPlatformBrowser(platformIds);
}
ngOnInit() {
this.getPropertiesFrDb();
}
getPropertiesFrDb() {
if (this.propertyWarehouseService.currentValuesProperties) {
this.propertyWarehouseService.getPropertyHome$.subscribe(
prop => {
console.log(prop);
return this.propertiesFrDb = prop
}
)
} else {
this.getPropertiesFrDbService.getHomeProperties()
.subscribe(property => {
// console.log(property['results']);
this.propertyWarehouseService.setPropertiesHome(property['results'])
return this.propertiesFrDb = property['results']
},
)
}
}
I want to test this.getPropertiesFrDb() in ngOnInit
i would like to test the case with this.propertyWarehouseService.currentValuesProperties !== ''and checked that this.getPropertiesFrDbService.getHomeProperties() is called and checked the value of propertiesFrDb
and my spec.ts file
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { MainSectionComponent } from './home-properties.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { MaterialModule } from 'src/app/material/material.module';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { GetPropertiesFrDbService } from 'src/app/services/getPropertiesFromDb/get-properties-fr-db.service';
import { MOCKPROPERTIES, MockPropertyWarehouseService } from 'src/app/mocks/property-post';
import { NgxPaginationModule, PaginatePipe } from 'ngx-pagination';
import { PropertyWarehouseService } from 'src/app/services/propertyWarehouse/property-warehouse.service';
import { BsDropdownModule } from 'ngx-bootstrap';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { StorageServiceModule } from 'angular-webstorage-service';
import { of } from 'rxjs/internal/observable/of';
fdescribe('MainSectionComponent', () => {
let component: MainSectionComponent;
let fixture: ComponentFixture<MainSectionComponent>;
const PROPERTYMODEL = MOCKPROPERTIES;
const spyPropertyWarehouseService = jasmine.createSpyObj('spypropertyWarehouseService', ['currentValuesProperties', 'getPropertyHome$']);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxPaginationModule,
BsDropdownModule.forRoot(),
NgbModule,
StorageServiceModule,
],
declarations: [
MainSectionComponent,
],
providers: [
{
provide: PropertyWarehouseService,
useValue: spyPropertyWarehouseService
}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MainSectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', (() => {
// console.log('properties', component);
expect(component).toBeTruthy();
}));
it('Should get propertiesFrDb from GetPropertiesFrDbService', async(() => {
spyPropertyWarehouseService.currentValuesProperties.and.returnValue(PROPERTYMODEL);
spyPropertyWarehouseService.getPropertyHome$.and.returnValue(of(PROPERTYMODEL));
expect(component.propertiesFrDb).toBe(PROPERTYMODEL);
console.log('spy',spyPropertyWarehouseService);
}));
});
Try creating a stub as below:
export class PropertyWarehouseServiceStub{
currentValuesProperties = '';
getPropertyHome$ = new BaheviorSubject<any>('someObj');
setPropertiesHome(){ }
}
export class GetPropertiesFrDbServiceStub{
getHomeProperties(){
return of({results: 'val'})
}
}
in component file make the service public in constructor so that we can override some of its behaviors:
constructor(...,
public propertyWarehouseService: PropertyWarehouseService,
public getPropertiesFrDbService: GetPropertiesFrDbService,
....)
and in spec file as :
providers: [
{
provide: PropertyWarehouseService,
useClass: PropertyWarehouseServiceStub
},{
provide: GetPropertiesFrDbService,
useClass: GetPropertiesFrDbServiceStub
}
],
......
....
..
it('should call getPropertiesFrDb() in ngOnInit',()=>{
spyOn(component,'getPropertiesFrDb').and.callThrough();
component.ngOnInit();
expect(component.getPropertiesFrDb).toHaveBeenCalled();
})
it('inside getPropertiesFrDb() should call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is empty,()=>{
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('someObj');
})
it('inside getPropertiesFrDb() should not call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is NOT empty,()=>{
component.propertyWarehouseService.currentValuesProperties = 'Not empty';
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).not.toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).not.toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('val');
})
You can refer to this intro article written by me on Karma-jasmine which contains more article references for several test use cases.
This one is very much similar to what you are looking for. I am planning to write some more articles, in case you wanna follow.
Also, I have no clue on why you are using return as below inside getPropertiesFrDb()
return this.propertiesFrDb = prop
which is of no use because no value of this function has been assigned to any variable inside ngOnInit.

Angular2 unit test. Check if a function calls router.navigate

I'm new to testing. Have a function that, when it's called, navigates to a new page. But the test is failing with an error:
Expected spy navigate to have been called with [ [
'./../admin/documents/12345' ] ] but it was never called.
public showProfile(id) {
this.router.navigate(['./../admin/documents/' + id]);
}
In my test file I have:
import {ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { AppComponent} from './app.component';
import {FormsModule} from "#angular/forms";
import {HttpClientTestingModule} from "#angular/common/http/testing";
import {Router} from "#angular/router";
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let mockRouter;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent],
imports: [FormsModule, HttpClientTestingModule],
providers: [
{ provide: Router, useValue: mockRouter }]
});
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
mockRouter = {
navigate: jasmine.createSpy('navigate')
};
});
describe('when showProfile() is called', () => {
it('navigates to a profile page',() => {
const id= 12345;
component.showProfile(id);
expect(mockRouter.navigate)
.toHaveBeenCalledWith(['./../admin/documents/' + id]);
});
});
});
You need to initialize the mockRouter before you use it in TestBed.configureTestingModule.
Also, expect(mockRouter.navigate).toHaveBeenCalledWith(['./../admin/documents/' + id]); won't work because ['a'] !== ['a']. As a workaround, you can do something like:
expect(mockRouter.navigate.calls.mostRecent().args[0][0])
.toEqual('./../admin/documents/' + id);
I've created a Stackblitz for your test.

how to check function is called or not in angular?

I am trying to test component in angular .following thing I need to test
service function is called or not
how to mock the response
here is my code
https://stackblitz.com/edit/angular-testing-w9towo?file=app%2Fapp.component.spec.ts
spec.ts
import { ComponentFixture,TestBed, async,getTestBed } from '#angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
describe('AppComponent', () => {
let fixture:ComponentFixture<AppComponent>,
component:AppComponent,
injector:TestBed,
service:AppService,
httpMock:HttpTestingController,
el:HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [
AppComponent
],
providers: [ AppService ]
}).compileComponents();
}));
afterEach(() => {
//httpMock.verify();
});
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
// injector = getTestBed();
// service = injector.get(AppService);
// httpMock = injector.get(HttpTestingController);
spyOn('appService',getData);
describe('AppComponent onit test', () => {
it('should create the app', async(() => {
expect(true).toBe(true);
}));
it('should called appService getData method', async(() => {
expect(appService.getData).toHaveBeenCalled();
}));
})
});
getting error
cannot read property 'injector' of null
you can mock the service that way:
const mockPosts: Posts = {
userId: 10,
id: 10,
title: "post",
body: "post"};
class MockAppService extends AppService{
public getData() {
return Observable.of(mockPosts)
}
}
and use that mock class in your providers instead of the service
{ provide: AppService, useClass: MockAppService },
and add this:
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
appservice = TestBed.get(AppService); // this line
you can spyOn that service and return a value like this
spyOn(appservice , 'getData').and.returnValue("your value")
final file
import { ComponentFixture,TestBed, async,getTestBed } from '#angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
import { Observable } from 'rxjs/Observable';
import { Posts } from './post.interface';
import 'rxjs/add/observable/of';
const mockPosts: Posts =
{userId: 10,
id: 10,
title: "post",
body: "post",};
class MockAppService extends AppService {
public getData(){
return Observable.of(mockPosts)
}
}
describe('AppComponent', () => {
let fixture:ComponentFixture<AppComponent>,
component:AppComponent,
injector:TestBed,
service:AppService,
httpMock:HttpTestingController,
el:HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [
AppComponent
],
providers: [
{ provide: AppService, useClass: MockAppService }
]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
service = TestBed.get(AppService)
// injector = getTestBed();
// service = injector.get(AppService);
// httpMock = injector.get(HttpTestingController);
spyOn(service,'getData');
}));
afterEach(() => {
//httpMock.verify();
});
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
service = TestBed.get(AppService)
// injector = getTestBed();
// service = injector.get(AppService);
// httpMock = injector.get(HttpTestingController);
spyOn(service,'getData');
describe('AppComponent onit test', () => {
it('should create the app', async(() => {
expect(true).toBe(true);
}));
it('should called appService getData method', async(() => {
fixture.detectChanges();
expect(service.getData).toHaveBeenCalled();
}));
})
});

Cannot read property 'getData' of undefined in angular

could you please tell me why I am getting this error Cannot read property 'getData' of undefined
I am trying to test my service when I try to run my test it gives me above error
here is my code
https://stackblitz.com/edit/angular-testing-w9towo?file=app%2Fapp.service.spec.ts
import { TestBed, ComponentFixture, async, inject } from '#angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '#angular/common/http/testing';
import {Posts} from './post.interface';
import { AppService } from './app.service';
describe('AppService', () => {
let service:AppService,
httpMock:HttpTestingController;
beforeEach(() => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AppService]
}).compileComponents();
service =TestBed.get(AppService);
httpMock =TestBed.get(HttpTestingController);
}));
afterEach(()=>{
httpMock.verify();
})
})
describe('Service is truty', () => {
it('should return an Observable<User[]>', () => {
const dummyUsers :Posts[]= [{
userId: 10,
id: 10,
title: 'post',
body: 'post'
}];
service.getData().subscribe(users => {
console.log(users)
expect(users.length).toBe(1);
expect(users).toEqual(dummyUsers);
});
const req= httpMock.expectOne('https://jsonplaceholder.typicode.com/posts')
expect(req.method).toBe('GET');
req.flush(dummyUsers);
});
})
})
After trying more I am getting Cannot read property 'getData' of undefined
Why do you use nested beforeEach() and compileComponents()? Your beforeEach() statement should look like this:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AppService]
});
service = TestBed.get(AppService);
httpMock = TestBed.get(HttpTestingController);
});
Try this:
import { TestBed, ComponentFixture, async, inject } from '#angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '#angular/common/http/testing';
import {Posts} from './post.interface';
import {HttpModule} from '#angular/http';
import { AppService } from './app.service';
describe('AppService', () => {
let service:AppService,
httpMock:HttpTestingController;
let fixture;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule,
HttpModule],
providers: [
AppService
]
}).compileComponents().then( () => {
service = TestBed.get(AppService);
httpMock =TestBed.get(HttpTestingController);
})
}));
afterEach(()=>{
httpMock.verify();
})
describe('Service is truty', () => {
it('should return an Observable<User[]>', () => {
const dummyUsers :Posts[]= [{
userId: 10,
id: 10,
title: 'post',
body: 'post'
}];
service.getData().subscribe(users => {
console.log(users)
expect(users.length).toBe(1);
expect(users).toEqual(dummyUsers);
});
const req= httpMock.expectOne('https://jsonplaceholder.typicode.com/posts')
//expect(req.method).toBe('GET');
req.flush(dummyUsers);
});
})
})
Live Demo:
https://stackblitz.com/edit/angular-testing-oqrffm?file=app%2Fapp.service.spec.ts

Categories