I have an Anuglar (version 8) application, that uses ngrx/store and RxJs for angular with this component:
export class CarComponent implements OnInit, OnDestroy {
// subscriptions
private unsubscribe$ = new Subject<void>();
constructor(
private activatedRoute: ActivatedRoute,
private readonly store: Store<any>,
private routingService: RoutingService,
private readonly carService: CarService) {}
ngOnInit(): void {
this.activatedRoute.queryParamMap.pipe(
takeUntil(this.unsubscribe$)).subscribe((paramMap: ParamMap) => {
this.store
.pipe(
select(selectDog, { dog: paramMap.get('carCode')})
)
.subscribe((car: Car) => {
this.setCar(Car);
}).unsubscribe();
});
}}
}
I have created an unit-test created with Karma and Jasmine:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
}
}
}]
})
}));
beforeEach(() => {
activatedRoute = TestBed.get(ActivatedRoute.prototype.queryParamMap.pipe());
router = TestBed.get(Router);
routingService = TestBed.get(RoutingService);
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
store.dispatch(new LoadScopeSuccess(scope()));
fixture = TestBed.createComponent(CarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('init', () => {
spyOn(activatedRoute.queryParamMap, 'pipe').and.returnValue(new Observable());
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});
});
But when I launch the test I have this error:
1) init
CarComponent
TypeError: Cannot read property 'pipe' of undefined
at Object.get queryParamMap [as queryParamMap] (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/#angular/router/fesm2015/router.js:2747:1)
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/monthly-view/monthly-view.component.spec.ts:90:59)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:359:1)
at ProxyZoneSpec.push.../../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-testing.js:308:
1)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:358:1)
at Zone.run (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:124:1)
[...]
I have tried to mock the activate route but I have not found a solution. Have you a solution in order to bypass this problem? I thank you very much.
You have to mock activatedRoute.queryParamMap and unfortunately since it's not a method, you can't use pipe. I would do this:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
// !! add this line
const mockQueryParamMap = new BehaviorSubject<any>({
get(value: string) => {
if (value === 'carCode') {
return '123'; // your mock for `carCode`
} else {
return '456';
}
}
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
},
// !! add this line
queryParamMap: mockQueryParamMap,
},
}]
})
}));
it('init', () => {
// this is why we use BehaviorSubject so we have full control
// on what to push into the subscription
mockQueryParamMap.next({
get(value: string) => {
if (value === 'carCode') {
return '5288-5547-5247-4'; // your mock for `carCode`
} else {
return '456';
}
}
});
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});
Related
I'm trying to unit test a component that requires a Resolver, Router and ActivatedRoute as dependencies. I've tried to use the RouterTestingModule and mock my resolver to provide them in the testing module, but it seems to have some side effects on the creation of the component instance.
Here is the code of my component:
History.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { Subscription } from 'rxjs';
import { Transaction } from '../models/transaction.model';
#Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss']
})
export class HistoryComponent implements OnInit, OnDestroy {
history: Transaction[] = [];
selectedTransaction: Transaction | undefined;
subscription: Subscription = new Subscription();
constructor(
private route: ActivatedRoute,
private router: Router,
) { }
ngOnInit(): void {
this.history = this.route.snapshot.data.history;
const routeSubscription = this.route.params.subscribe((params) => {
if (params.id) {
this.setSelectedTransaction(+params.id);
}
});
this.subscription.add(routeSubscription);
}
setSelectedTransaction(transactionId: number): void {
const historyTransaction = this.history.find((transaction) => transaction.id === transactionId);
this.selectedTransaction = historyTransaction;
}
displayTransaction(transaction: Transaction): void {
this.router.navigate(['transactions', transaction.id]);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
And here is the current unit test:
History.spec.ts
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { ActivatedRoute, Resolve, Routes } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { Observable, of } from 'rxjs';
import { Transaction } from '../models/transaction.model';
import { HistoryComponent } from './history.component';
import { HistoryResolver } from './history.resolver';
const mockRawTransactions = [
{
"id":"1",
"created_at":"2016-01-01T08:30:39-0300",
"counterparty_name":"Uber",
"debit":"false",
"credit":"true",
"amount":"44.20",
"currency":"EUR",
"operation_type":"refund",
"attachements":[
{
"url":"https:\/\/fakeimg.pl\/350x200\/?text=Hello"
}
],
}
];
const mockTransactions = mockRawTransactions.map((transaction) => new Transaction(transaction));
class HistoryMockResolver implements Resolve<Transaction[]> {
resolve(): Observable<Transaction[]> {
return of(mockTransactions);
}
}
describe('HistoryComponent', () => {
const historyRoutes: Routes = [
{ path: 'transactions', component: HistoryComponent },
{ path: 'transactions/:id', component: HistoryComponent },
];
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HistoryComponent ],
imports: [
RouterTestingModule.withRoutes(historyRoutes),
],
providers: [
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
},
},
},
{ provide: HistoryResolver, useClass: HistoryMockResolver },
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
console.log('Component', component);
expect(component).toBeTruthy();
});
it('should display transactions', () => {
component.history = mockTransactions;
expect(component.history).toBeDefined();
expect(component.history).toHaveSize(mockRawTransactions.length);
});
it('should display a single transaction', () => {
component.history = mockTransactions;
component.displayTransaction(component.history[0]);
expect(component.selectedTransaction).toBeDefined();
expect(component.selectedTransaction).toEqual(component.history[0]);
});
});
The component is defined and well displayed in the console.log while running the tests in Karma, but Karma raises errors for each test case and evaluates the component as undefined.
Here is the first error:
TypeError: Cannot read property 'history' of undefined
at HistoryComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/history/history.component.ts:22:45)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2486:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2457:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:2408:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9207:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9306:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10532:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10557:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:22569:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/testing.js:141:1)
What should I fix in my testing module ?
This is because of this.route.snapshot.data.history and data being undefined as your have not passed it in your mock activated snapshot.
You can update your provider for activated route snapshot in History.spec.ts
{
provide: ActivatedRoute,
useValue: {
snapshot: {
params: { id: 1 },
data: {history: 'something-history-obj'}
},
},
},
Or you can always use this.route.snapshot.data?.history within History.component.ts if it is indeed nullable
This is my code in angular, the functionality is working all fine but the test cases are getting failed. Please tell me what i am doing wrong in the code?
The error I am getting
HeadlessChrome 83.0.4103 (Windows 10.0.0) AboutComponent should create FAILED
TypeError: Cannot read property 'getAboutInfo' of undefined
at **
at AboutComponent.ngOnInit (http://localhost:9876/karma_webpack/src/app/about/about.component.ts:44:28)
at callHook (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3937:1)
at callHooks (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3901:1)
at executeInitAndCheckHooks (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:3842:1)
at refreshView (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11795:1)
at renderComponentOrTemplate (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11903:1)
at tickRootContext (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:13379:1)
at detectChangesInRootView (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:13413:1)
at RootViewRef.detectChanges (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:15093:22)
at ComponentFixture._tick (http://localhost:9876/karma_webpack/node_modules/#angular/core/ivy_ngcc/fesm2015/testing.js:323:1)
import { async, ComponentFixture, TestBed} from '#angular/core/testing';
import { AboutComponent } from './about.component';
import { AboutService } from './about.service';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { Observable, of } from 'rxjs';
import { I18nService } from 'src/utils/i18n.service';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { AppModule } from './../app.module';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
let dialogSpy: jasmine.Spy;
let app: any;
const mockDialogRef = {
close: jasmine.createSpy('close')
};
let service: any;
const data = '20/04/2019';
let getAboutInfoSpy: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AboutComponent],
imports: [HttpClientTestingModule , AppModule],
providers: [{ provide: AboutService, useValue: service },
I18nService,
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: mockDialogRef}]
}).compileComponents();
}));
beforeEach(async () => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
await fixture.whenStable();
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('infoList should be empty array', () => {
expect(app['dataList'].length).toBe(0);
});
it('when OnInit invoked through service data will return to infoList ', async(() => {
service = fixture.debugElement.injector.get(AboutService);
spyOn(service, 'getAboutInfo').and.returnValue(of(data));
app.ngOnInit();
expect(app['dataList'].length).toBe(3);
}));
it('onCancel should close the dialog', async( () => {
component.closePopup();
expect(mockDialogRef.close).toHaveBeenCalled();
}));
});
import { Component, OnInit, Inject } from '#angular/core';
import { AboutService } from './about.service';
import { Subscription } from 'rxjs';
import { MatDialogRef} from '#angular/material/dialog';
import { I18nService } from 'src/utils/i18n.service';
#Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent implements OnInit {
private aboutServiceSubscription: Subscription;
dataList: any;
locales: any = {};
translator: any;
constructor(
private dialogRef: MatDialogRef<AboutComponent>,
public aboutService: AboutService,
private i18Service: I18nService) {}
ngOnInit() {
this.translator = this.i18Service.getTranslator();
this.translator.translateObject.subscribe((item: any) => {
this.locales = item;
});
this.aboutServiceSubscription = this.aboutService.getAboutInfo().subscribe((data: any) => {
if (data) {
data = data.split('/');
this.dataList = data;
}
});
}
/**
* Closes the poup
* #memberof AboutComponent
*/
closePopup() {
this.dialogRef.close();
}
}
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AboutService {
constructor(private http: HttpClient) {
}
getAboutInfo() {
return this.http.get('/assets/aboutInfo.txt', {responseType: 'text'})
}
}
The error message indicates that you did not mock AboutService correctly, you passed an undefined to useValue, so the AboutService obtained through the Injector is undefined. You can't use spyOn to a undefined value.
Here is a working example, with irrelevant code removed:
about.component.ts:
import { Component, OnInit } from '#angular/core';
import { AboutService } from './about.service';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-about',
})
export class AboutComponent implements OnInit {
private aboutServiceSubscription: Subscription;
dataList: any;
locales: any = {};
translator: any;
constructor(public aboutService: AboutService) {}
ngOnInit() {
this.aboutServiceSubscription = this.aboutService
.getAboutInfo()
.subscribe((data: any) => {
if (data) {
data = data.split('/');
this.dataList = data;
}
});
}
}
about.service.ts:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class AboutService {
constructor(private http: HttpClient) {}
getAboutInfo() {
return this.http.get('/assets/aboutInfo.txt', { responseType: 'text' });
}
}
about.component.spec.ts:
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { of } from 'rxjs';
import { AboutComponent } from './about.component';
import { AboutService } from './about.service';
fdescribe('62700708', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
let aboutServiceSpy: jasmine.SpyObj<AboutService>;
const data = '20/04/2019';
beforeEach(() => {
aboutServiceSpy = jasmine.createSpyObj('AboutService', ['getAboutInfo']);
aboutServiceSpy.getAboutInfo.and.returnValue(of(data));
TestBed.configureTestingModule({
declarations: [AboutComponent],
imports: [HttpClientTestingModule],
providers: [{ provide: AboutService, useValue: aboutServiceSpy }],
}).compileComponents();
});
beforeEach(async () => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('when OnInit invoked through service data will return to infoList ', () => {
expect(aboutServiceSpy.getAboutInfo).toHaveBeenCalledTimes(1);
expect(component.dataList).toEqual(['20', '04', '2019']);
});
});
unit test result:
I am trying to set up Authorisation in NestJs using Mongoose via a User Service. I'm getting the following errors when trying to Unit Test:
I understand that the UserModel is injected into the UsersService and needs to be resolved when using the UsersModule but I can't work out how to do this when creating the TestingModule. Please can someone shed some light on this for me?
Code is below, thanks:
USER MODULE
## users.module.ts
import { Module } from '#nestjs/common';
import { UsersService } from './users.service';
#Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
## users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './interfaces/user.interface';
import { CreateUserDto } from './dto/create-user.dto';
#Injectable()
export class UsersService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async store(userData: CreateUserDto): Promise<User> {
const newUser = new this.userModel(userData);
return newUser.save();
}
async find(data: Record<string, any> = {}) {
return this.userModel
.findOne(data)
.exec();
}
async findById(id: string): Promise<User> {
return this.userModel
.findById(id)
.exec();
}
async findByIdOrFail(id: string): Promise<User> {
return this.userModel
.findById(id)
.orFail()
.exec();
}
async update(id: string, data: Record<string, any> = {}): Promise<User> {
return this.userModel
.findByIdAndUpdate(id, data, {
runValidators: true,
useFindAndModify: false,
new: true,
})
.orFail()
.exec();
}
async destroy(id: string): Promise<User> {
return this.userModel
.findByIdAndRemove(id, {
useFindAndModify: false,
})
.orFail()
.exec();
}
}
AUTH MODULE
## auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
#Module({
imports: [UsersModule],
providers: [AuthService]
})
export class AuthModule {}
## auth.service.ts
import { Injectable } from '#nestjs/common';
import { UsersService } from '../users/users.service';
#Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
}
AUTH SERVICE TEST
## auth.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { MongooseModule } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { UserSchema } from '../users/schemas/user.schema';
const mongod = new MongoMemoryServer();
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const uri = await mongod.getUri();
const module: TestingModule = await Test.createTestingModule({
imports: [
MongooseModule.forRoot(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
UsersModule,
],
providers: [
AuthService
],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
In unit tests, you should mock all necessary dependencies for AuthService. It's not e2e test, you shouldn't initialize MongooseModule here.
Your beforeEach should look like that:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{ provide: UsersService, useValue: createSpyObj(UsersService) }
]
}).compile();
service = module.get<AuthService>(AuthService);
});
Combining Maciej's suggestion above and this blog: https://medium.com/#davguij/mocking-typescript-classes-with-jest-8ef992170d1 , I arrived at the following solution:
## /auth/auth.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
jest.mock('../users/users.service');
describe.only('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
UsersModule,
],
providers: [
AuthService,
],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should validate user ok', async () => {
const res = await service.validateUser('username');
expect(res).toBeDefined();
});
});
I also had to create a mock class, under mocks as suggested in the above article:
## /users/__mocks__/users.service.ts
import { Injectable } from '#nestjs/common';
#Injectable()
export class UsersService {
async find(data: Record<string, any> = {}) {
return {
"firstname": "firstname",
"lastname": "lastname"
}
}
}
which mocks the below class (same as above, but reduced for the sake of this explanation):
## /users/users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './interfaces/user.interface';
#Injectable()
export class UsersService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async find(data: Record<string, any> = {}) {
return this.userModel
.findOne(data)
.exec();
}
}
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.
Im creating a unit test for my confirmation modal that uses MatDialog. my first test is a basic test that the component should be created. here is my code for the spec file.
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { PocitConfirmationModalComponent } from './confirmation-modal.component';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { CommonModule } from '#angular/common';
import { PortalModule } from '#angular/cdk/portal';
import { MaterialModule } from 'src/app/core/material/material.module';
import { BrowserDynamicTestingModule } from '#angular/platform-browser-dynamic/testing';
class MatDialogRefStub {
close(param: string) {}
}
describe('PocitConfirmationModalComponent', () => {
let component: PocitConfirmationModalComponent;
let fixture: ComponentFixture<PocitConfirmationModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
MaterialModule,
PortalModule
],
declarations: [PocitConfirmationModalComponent],
providers: [
{ provide: MatDialogRef, useClass: MatDialogRefStub },
{ provide: MAT_DIALOG_DATA, useValue: {} },
]
}).overrideModule(BrowserDynamicTestingModule, {
set: {
entryComponents: [ PocitConfirmationModalComponent ],
}
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PocitConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
this is the component file that I want to test.
import { Component, OnInit, Inject, ViewEncapsulation } from '#angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { ComponentPortal } from '#angular/cdk/portal';
#Component({
selector: 'pocit-confirmation-modal',
templateUrl: './confirmation-modal.component.html',
styleUrls: ['./confirmation-modal.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class PocitConfirmationModalComponent implements OnInit {
portal: ComponentPortal<any>;
constructor(
public dialogRef: MatDialogRef<PocitConfirmationModalComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
) {
this.portal = new ComponentPortal(this.data.component);
}
ngOnInit() {
}
action(type: string) {
this.dialogRef.close(type);
}
}
after all of these I got this error after running the test.
Error: No component factory found for undefined. Did you add it to #NgModule.entryComponents?
I already added it to entryComponents but still got this error.
//your public data:any model, eg:
const model: any = {
ActionButton: 'Delete',
SupportingText: 'Are you sure?',
};
then in providers:
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: model
}
]