I am trying to write a unit test for my angular component and I am stuck at this error. I have been debugging for a long time with no luck. Any help is appreciated... I have created the mockActivatedRoute in spec.ts but it keeps showing the below error. How come the paramMap property cannot be read? Here is the error message I get:
TypeError: Cannot read properties of undefined (reading 'paramMap')
Below is my code:
.ts file
import { Component, OnChanges, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { CartItem } from 'src/app/common/cart-item';
import { Product } from 'src/app/common/product';
import { AuthService } from 'src/app/services/auth.service';
import { CartService } from 'src/app/services/cart.service';
import { ProductService } from 'src/app/services/product.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
products: Product[] = [];
currentCategoryId: number = 0;
previousCategoryId: number = 1;
currentCategoryName: String | null = null;
searchMode: boolean = false;
pageNumber: number = 1;
pageSize: number = 10;
totalElements: number = 10;
isAdmin: boolean = false;
constructor(private productService: ProductService,
private route: ActivatedRoute,
private cartService: CartService,
private router: Router,
private authService: AuthService
) { }
ngOnInit(): void {
this.route.paramMap.subscribe(() => {
this.productsList();
});
this.authService.isAdmin.subscribe((res) => {
this.isAdmin = res;
})
}
productsList() {
this.searchMode = this.route.snapshot.paramMap.has("keyword");
if (this.searchMode) {
this.handleSearchProductsList();
} else {
this.handleProductsList();
}
}
handleSearchProductsList() {
const keyword: string = this.route.snapshot.paramMap.get("keyword")!;
this.productService.getSearchProducts(keyword).subscribe((res) => {
this.products = res;
});
}
handleProductsList() {
// check if there is id in activatedroute
const hasCategoryId: boolean = this.route.snapshot.paramMap.has("id");
if (hasCategoryId) {
// get the id from the route + is required since the get return a string
this.currentCategoryId = +this.route.snapshot.paramMap.get("id")!;
this.currentCategoryName = this.route.snapshot.paramMap.get("name");
this.productService.getProductListByCategoryId(this.currentCategoryId).subscribe((res) => {
this.products = res;
})
} else {
this.productService.getProductList().subscribe((res) => {
this.products = res;
})
}
}
addToCart(product: Product) {
// subscribe the cart servie add to cart
this.cartService.addToCart(new CartItem(product))
}
deleteProduct(product: Product) {
this.productService.deleteProduct(product.id).subscribe((res) => {
// console.log(res)
if (res.success) {
this.handleProductsList()
}
})
}
}
.ts.spec
import { HttpClient, HttpHandler } from '@angular/common/http';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap, RouteConfigLoadEnd } from '@angular/router';
import { async, Observable, of, Subject } from 'rxjs';
import { Product } from 'src/app/common/product';
import { AuthService } from 'src/app/services/auth.service';
import { ProductService } from 'src/app/services/product.service';
import { ProductListComponent } from './product-list.component';
describe('ProductListComponent', () => {
let PRODUCTS: Product[];
let mockProductService: any;
let mockAuthService: any
let mockActivatedRoute: any;
let component: ProductListComponent;
let fixture: ComponentFixture<ProductListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProductListComponent ],
providers: [
{ provide: ProductService, useValue: mockProductService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: AuthService, useValue: mockAuthService }
]
})
.compileComponents();
mockAuthService = jasmine.createSpyObj({}, { isAdmin: new Subject() })
mockProductService = jasmine.createSpyObj(['getProductList', 'getProducts', 'deleteProduct'])
mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})
PRODUCTS = [
{
id: 1,
sku: "test1",
name: "test1",
description: "test1",
unitPrice: 1,
imageUrl: "test1",
active: true,
unitsInStock: 1,
category: 1,
dateCreated: new Date(),
lastUpdated: new Date()
},
{
id: 2,
sku: "test2",
name: "test2",
description: "test2",
unitPrice: 2,
imageUrl: "test2",
active: true,
unitsInStock: 2,
category: 2,
dateCreated: new Date(),
lastUpdated: new Date()
}
]
fixture = TestBed.createComponent(ProductListComponent);
component = fixture.componentInstance;
mockAuthService.isAdmin.next(false);
mockActivatedRoute.paramMap.and.returnValue(of([]) )
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
expect(component.isAdmin).toBe(false)
});
it("should delete the selected product from products", () => {
component.products = PRODUCTS;
mockProductService.deleteProduct.and.returnValue(
of({ success: true, status: 200, message:"you product has been deleted" }));
component.deleteProduct(PRODUCTS[0]);
expect(component.handleProductsList).toHaveBeenCalled();
})
});
I have tried to search online for solutions on this issue with no luck. I am expecting for this unit test to work but I am unsure on how to fix the issue.
It seems that you are defining your mock after you've declared the provider. So Angular injects undefined
for ActivatedRoute
. Here's a distilled version of your problem:
describe('ProductListComponent', () => {
// 1. you declare `mockActivatedRoute`; its value is `undefined` at this point
let mockActivatedRoute: any;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProductListComponent ],
providers: [
// 2. you define a custom provider for `ActivatedRoute`;
// this provider uses a value stored in `mockActivatedRoute`,
// which is still `undefined` at this point
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
]
})
.compileComponents();
// 3. you assign a value to `mockActivatedRoute`;
// unfortunately it is too late - the provider has already been defined...
mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})
To fix it make sure to assign correct values to all your mocks before the providers are declared:
beforeEach(async () => {
mockAuthService = jasmine.createSpyObj({}, { isAdmin: new Subject() })
mockProductService = jasmine.createSpyObj(['getProductList', 'getProducts', 'deleteProduct'])
mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})
await TestBed.configureTestingModule({
declarations: [ ProductListComponent ],
providers: [
{ provide: ProductService, useValue: mockProductService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: AuthService, useValue: mockAuthService }
]
})
.compileComponents();
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.