NestJs Is not injecting value in jest unit test - unit-testing

I cant figure out why nestjs is not injecting value into my contstructor
Controller
export class HealthCheckController {
constructor(#Inject('HealthCheckService') private healthCheckService: HealthCheckService, private appLogger: AppLogger) {
}
Then my test
beforeEach(async () => {
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
AppLoggerNope.getProvider(),
{ provide: 'HealthCheckService', useClass: HealthCheckService },
],
})
.compile();
controller = await module.resolve<HealthCheckController>(HealthCheckController);
const logger = await module.resolve<AppLogger>(AppLogger);
service = await module.resolve<HealthCheckService>('HealthCheckService');
});
NopeLogger
export class AppLoggerNope {
static getProvider() {
return {
provide: AppLogger,
useValue: {}
}
}
}
So if I debug my test I can see that instance of service is supplied but logger is null.
Now what is interesting if I comment appLogger in provider controller will be created but it will fail await module.resolve<AppLogger>(AppLogger);

You don't need to explicitly define an injection token for the HealthCheckService with #Inject, Nest will resolve it for you. You also don't need to use module.resolve unless you're using request-scoped providers. I've also pulled out the provider from your AppLoggerNope static method for simplicity. If this is working, then you can start to get fancy and move those values elsewhere.
Controller:
export class HealthCheckController {
constructor(private healthCheckService: HealthCheckService, private appLogger: AppLogger) { }
Test file:
beforeEach(async () => {
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
{ provide: AppLogger, useValue: {} },
HealthCheckService,
],
})
.compile();
controller = module.get<HealthCheckController>(HealthCheckController);
const logger = module.get<AppLogger>(AppLogger);
service = module.get<HealthCheckService>(HealthCheckService);
});
Note that the HealthCheckService is supplied directly as a provider in the testing module. Unless you need to mock something or stub out some kind of functionality, you can directly use it as a provider in your testing module. If you needed to be able to assert that some method within HealthCheckService was called in a test, you may want to do something like this:
const mockedHealthCheck = { someMethod: jest.fn() };
module = await Test.createTestingModule({
controllers: [HealthCheckController],
providers: [
{ provide: AppLogger, useValue: {} },
{ provide: HealthCheckService, useValue: mockedHealthCheck,
],
})
.compile();
Then in your test you can assert expect(mockedHealthCheck.someMethod).toBeCalled().

Related

How to test NestJS Controller if it has a lot of dependent services?

I have a trouble with Testing Nest JS Controller because I cannot realise how to make a Testing Module with all the dependencies. I've already tried Mocks but still it's not working.
Here's how the controller I want to test looks like
calculator.controller.ts
#Controller('/calculator')
export class CalculatorController {
constructor(
#Inject(HISTORY_SERVICE)
private historyService: HistoryService,
#Inject(CACHE_SERVICE)
private readonly cacheService: CacheService,
#Inject(CALCULATOR_SERVICE)
private readonly calculatorService: CalculatorService,
) {}
#Get()
getResult(#Query() expressionDto: ExpressionDto): Promise<ClientResponseDto> {
const expression: string = expressionDto.expression;
const response: Promise<ClientResponseDto> = this.cacheService
.checkInCache(expression)
.then((response) => {
if (!response) {
const calculationResult =
this.calculatorService.getResult(expression);
const clientDto = this.historyService
.create({
expression,
result: calculationResult,
})
.then((dbResponse) => {
return this.cacheService.setToCache(dbResponse);
});
return clientDto;
}
return this.historyService.create({ expression, result: response });
});
return response;
}
}
And this is how it's spec looked like before mocks implementation
controller.spec.ts
let calculatorController: CalculatorController;
let calculatorService: CalculatorService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [HistoryModule],
controllers: [CalculatorController],
providers: [
CalculatorService,
],
})
.useMocker(() => createMock())
.compile();
calculatorController =
moduleRef.get<CalculatorController>(CalculatorController);
calculatorService = moduleRef.get<CalculatorService>(CalculatorService);
jest.clearAllMocks();
});
describe('Calculator Controller', () => {
it('should be defined', () => {
expect(calculatorController).toBeDefined();
});
it('should have all methods', () => {
expect(calculatorController.getResult).toBeDefined();
expect(calculatorController.getResult(calculatorStub().request)).toBe(
typeof Promise,
);
});
});
And this test failed when calling getResult function cause inside this Function firstly I call CacheService to check data in Cache. So at this moment test failed telling that
TypeError: this.cacheService.checkInCache(...).then is not a function
24 | const response: Promise<ClientResponseDto> = this.cacheService
25 | .checkInCache(expression)
> 26 | .then((response) => {
| ^
I started to think that the problem is Testing module somehow doesn't have access to the Cache Service, so I added mock to the providers like this
let calculatorController: CalculatorController;
let calculatorService: CalculatorService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [HistoryModule],
controllers: [CalculatorController],
providers: [
CalculatorService,
{
provide: CacheService,
useValue: {
checkInCache: jest.fn().mockResolvedValue(Promise<null>),
},
},
],
})
.useMocker(() => createMock())
.compile();
calculatorController =
moduleRef.get<CalculatorController>(CalculatorController);
calculatorService = moduleRef.get<CalculatorService>(CalculatorService);
jest.clearAllMocks();
});
But now tests don't even run cause I have Nest dependencies problems
Nest can't resolve dependencies of the CalculatorController (HISTORY_SERVICE, ?,
CALCULATOR_SERVICE). Please make sure that the argument dependency at index [1] is
available in the RootTestModule context.
What is the issue and how is it possible to solve this problem?
Generally speaking, when unit testing a service or a controller, you want to provide mocks for the controller's or service's dependencies. Most of the time, these are going to be objects with the same method names but the methods are set to be jest.fn() or similar for other mock libraries. You'll want to use custom providers to create the mock providers that will be injected. Taking your controller above, you'll want the setup of your test to look something like this:
describe('CaclulatorController', () => {
let controller: CalculatorController;
let service: Pick<jest.MockedObject<CalculatorService>, 'getResult'>;
let cache: Pick<jest.MockedObject<CacheService>, 'checkInCache' | 'setToCache'>;
let history: Pick<jest.MockedObject<HistoryService>, 'create'>;
beforeAll(async () => {
const modRef = await Test.createTestModule({
controller: [CalculatorController],
providers: [
{
provide: CALCULATOR_SERVICE,
useValue: {
getResult: jest.fn(),
},
},
{
provide: CACHE_SERVICE,
useValue: {
checkInCache: jest.fn(),
setToCache: jest.fn(),
},
},
{
provide: HISTORY_SERVICE,
useValue: {
create: jest.fn(),
},
},
]
}).compile();
controller = app.get(CalculatorController);
service = app.get(CALCULATOR_SERVICE);
cache = app.get(CACHE_SERVICE);
history = app.get(HISTORY_SERVICE);
});
Okay that's a lot to look at at once, so let's step through the big parts and explain what's going on here. The first this I do is set up local variables to reference during the test for the class that I'm testing (CalculatorController) and the dependencies of the class so I can modify them as necessary. Next, I use a Pick<T, K> generic with the jest.MockedOject<T> generic to tell Typescript that "This class has been mocked by jest, and I only am worried about these methods of it" so later on when I use cache. I'll get some intellisense for the checkInCache and setToCache methods, and they'll have jest's mock function types.
In the beforeAll I set up the initial mocks for the dependencies, you can also set return values here using the appropriate mockReturnValue or mockResolvedValue methods.
Now that the mocks and dependencies are set up, we can actually write a test. My approach is to use a describe block per method and its per variation of the method's outcome and branches. I'll write a single branch to show you and let you work out the rest from there.
describe('getResult', () => {
it('should get no response from the cache and perform a full caclulation', async () => {
cache.checkInCache.mockResolvedValueOnce(undefined);
service.getResult.mockResolvedValueOnce(calculationResult);
histoy.create.mockResolvedValueOnce(dbResult);
cache.setInCache.mockResolvedValueOnce(cacheSaveResult);
await expect(controller.getResult({ expression: someExpression })).resolves.toEqual(cacheSaveResult)
});
})
This should cover the case where there's no value in the cache and the full set of steps has to be taken. By using mockResolvedValueOnce we ensure that the methods don't return if called more than once as that's most likely not the expected case here, and we're making sure to return promsies as you use .thens. You might want to look into async/await syntax to help clean that up.
putting the two snippets together we have the following:
describe('CaclulatorController', () => {
let controller: CalculatorController;
let service: Pick<jest.MockedObject<CalculatorService>, 'getResult'>;
let cache: Pick<jest.MockedObject<CacheService>, 'checkInCache' | 'setToCache'>;
let history: Pick<jest.MockedObject<HistoryService>, 'create'>;
beforeAll(async () => {
const modRef = await Test.createTestModule({
controller: [CalculatorController],
providers: [
{
provide: CALCULATOR_SERVICE,
useValue: {
getResult: jest.fn(),
},
},
{
provide: CACHE_SERVICE,
useValue: {
checkInCache: jest.fn(),
setToCache: jest.fn(),
},
},
{
provide: HISTORY_SERVICE,
useValue: {
create: jest.fn(),
},
},
]
}).compile();
controller = app.get(CalculatorController);
service = app.get(CALCULATOR_SERVICE);
cache = app.get(CACHE_SERVICE);
history = app.get(HISTORY_SERVICE);
});
describe('getResult', () => {
it('should get no response from the cache and perform a full caclulation', async () => {
cache.checkInCache.mockResolvedValueOnce(undefined);
service.getResult.mockResolvedValueOnce(calculationResult);
histoy.create.mockResolvedValueOnce(dbResult);
cache.setInCache.mockResolvedValueOnce(cacheSaveResult);
await expect(controller.getResult({ expression: someExpression })).resolves.toEqual(cacheSaveResult)
});
});
});
That should be enough to get you started on testing the rest of your controller. If you need more test setup examples, there's an entire GitHub repository of them with different setups

NestJS service testing: try to test a simple save

I have this particular error: "TypeError: this.commentRepository.save is not a function"
When I run this simple test:
describe('CommentService', () => {
let commentService: CommentService;
const mockCommentRepository = {
createComment: jest.fn(comment => comment),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentService,
{
provide: getRepositoryToken(Comment),
useValue: mockCommentRepository,
}
],
}).compile();
commentService = module.get<CommentService>(CommentService);
});
it('should create a comment', async () => {
expect(await commentService.createComment({ message: 'test message'})).toEqual({
id: expect.any(Number),
message: 'test message',
});
});
});
The service:
async createComment(comment: Partial<Comment>): Promise<Comment> {
return await this.commentRepository.save(comment);
}
Can someone help me?
Your mockCommentRepository does not have save() method you are calling in your service createComment method. Mock this method as well to get rid of the error.
Also refer to this answer for more info on repository mocking https://stackoverflow.com/a/55366343/5716745
Since you are trying to create a provider for the Repository you have to mock the repository methods (in this case the save function).
{
provide: getRepositoryToken(Comment),
useValue: {
save: jest.fn().mockResolvedValue(null),
},
},

Angular2, jasmine: Component changes not seen in test spec function

I'm still writing tests for my Angular app. I've a test that modifies an Org object, saves the changes, and then proves that the changes have been kept. However, the test isn't seeing the changes.
My mock Org service that saves the changes:
#Injectable()
export class MockOrgService {
constructor() { }
public save(org: Org): Observable<Org> {
let savedOrg: Org = new Org(org);
savedOrg.address2 = 'Saved with id: ' + org.id;
return Observable.of(savedOrg);
}
}
My mock router:
beforeEach(async(() => {
routeStub = { data: Observable.of( { org: org1 } ), snapshot: {} } ;
TestBed.configureTestingModule({
imports: [ FormsModule, RouterTestingModule ],
providers : [
{ provide: DialogService, useClass: MockDialogService },
{ provide: GlobalsService, useClass: MockGlobalsService },
{ provide: OrgService, useClass: MockOrgService },
{ provide: ActivatedRoute, useValue: routeStub }
],
declarations: [ OrgDetailComponent ],
})
.compileComponents();
}));
My component function being tested:
private gotoParent(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
public save(): void {
this.error = null;
let that = this;
this.orgService
.save(that.org)
.subscribe(
(org: Org): void => {
that.org = org;
that.savedOrg = new Org(org);
that.gotoParent();
},
error => this.error = error
);
}
My test:
it('responds to the Save click by saving the Org and refilling the component', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
comp.org.id = 2;
comp.org.name = 'Another Org';
let elButton = fixture.debugElement.query(By.css('#save'));
elButton.nativeElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.error).toBeNull();
expect(comp.savedOrg.id).toEqual(2);
expect(comp.savedOrg.name).toEqual('Another Org');
expect(routeStub).toHaveBeenCalledWith(['../']);
});
});
When I use breakpoints I see that the OrgService.save() is called when click() is run, and that in the component save() function the that.savedOrg is being set. But when the test gets into the expect() functions comp.savedOrg is at its original value. It is as though there are two component instances.
FWIW, after setting, or not setting, my savedOrg my function then tries to route. I instead get an error:
Error: Expected a spy, but got Object({ data: ScalarObservable({ _isScalar: true, value: Object({ org: Org({ id: 2, [SNIP]
I'm not sure what I'm supposed to do to tell that the "goToParent" routing has been called.
Thanks in advance for help,
Jerome.
I figured out the "not seen in test spec function" issue. I am missing a line, right after the first whenStable(), which should be:
comp = fixture.componentInstance;
That makes everything sync OK. Now I must figure out how to make route testing work. That's another job.
Jerome.

Angular 2 Unit Testing prepare mocking to share in components

I'm doing unit test in my Angular2 application and very new to angular2 test framework.
I have 2 components and calling same method from a service.
Firstly, I need to import the service and model to create the mock
import { MyService } from '../../shared/my.service';
import { MyModel } from '../../shared/my.model';
Secondly, arrange the model and service to mock
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
const mockMyModel: MyModel[] = [];
const mockMyService = {
sameMethod: () => Observable.of(mockMyModel),
};
Finally, mock the service
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{ provide: MyService , useValue: mockMyService },
],
},
})
.compileComponents();
}));
I need to do the same thing again for my another component.
Option 1: is there any way to globally share the preparation for mock
e.g. { provide: MyService , useValue: global.mockMyService }
Option 2: is it possible to have 'ServiceTestingModule' such as 'RouterTestingModule' just to mock a service.
Thanks!
Option 1 definitely can be used
myMockProvider = { provide: MyService , useValue: mockMyService };
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent],
})
.overrideComponent(MyComponent, {
set: {
providers: [myMockProvider],
},
})
.compileComponents();
}));
myMockProvider can also be a list of providers. Nested lists are resolved/flattened by providers: [...] automatically.

Mock custom service in angular2 during unit test

I'm trying to write a unit test for component used in my service.
Component and service work fine.
Component:
import {Component} from '#angular/core';
import {PonyService} from '../../services';
import {Pony} from "../../models/pony.model";
#Component({
selector: 'el-ponies',
templateUrl: 'ponies.component.html',
providers: [PonyService]
})
export class PoniesComponent {
ponies: Array<Pony>;
constructor(private ponyService: PonyService) {
this.ponies = this.ponyService.getPonies(2);
}
refreshPonies() {
this.ponies = this.ponyService.getPonies(3);
}
}
Service:
import {Injectable} from "#angular/core";
import {Http} from "#angular/http";
import {Pony} from "../../models/pony.model";
#Injectable()
export class PonyService {
constructor(private http: Http) {}
getPonies(count: number): Array<Pony> {
let toReturn: Array<Pony> = [];
this.http.get('http://localhost:8080/js-backend/ponies')
.subscribe(response => {
response.json().forEach((tmp: Pony)=> { toReturn.push(tmp); });
if (count && count % 2 === 0) { toReturn.splice(0, count); }
else { toReturn.splice(count); }
});
return toReturn;
}}
Component unit test:
import {TestBed} from "#angular/core/testing";
import {PoniesComponent} from "./ponies.component";
import {PonyComponent} from "../pony/pony.component";
import {PonyService} from "../../services";
import {Pony} from "../../models/pony.model";
describe('Ponies component test', () => {
let poniesComponent: PoniesComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PoniesComponent, PonyComponent],
providers: [{provide: PonyService, useClass: MockPonyService}]
});
poniesComponent = TestBed.createComponent(PoniesComponent).componentInstance;
});
it('should instantiate component', () => {
expect(poniesComponent instanceof PoniesComponent).toBe(true, 'should create PoniesComponent');
});
});
class MockPonyService {
getPonies(count: number): Array<Pony> {
let toReturn: Array<Pony> = [];
if (count === 2) {
toReturn.push(new Pony('Rainbow Dash', 'green'));
toReturn.push(new Pony('Pinkie Pie', 'orange'));
}
if (count === 3) {
toReturn.push(new Pony('Fluttershy', 'blue'));
toReturn.push(new Pony('Rarity', 'purple'));
toReturn.push(new Pony('Applejack', 'yellow'));
}
return toReturn;
};
}
Part of package.json:
{
...
"dependencies": {
"#angular/core": "2.0.0",
"#angular/http": "2.0.0",
...
},
"devDependencies": {
"jasmine-core": "2.4.1",
"karma": "1.2.0",
"karma-jasmine": "1.0.2",
"karma-phantomjs-launcher": "1.0.2",
"phantomjs-prebuilt": "2.1.7",
...
}
}
When I execute 'karma start' I get this error
Error: Error in ./PoniesComponent class PoniesComponent_Host - inline template:0:0 caused by: No provider for Http! in config/karma-test-shim.js
It looks like karma uses PonyService instead of mocking it as MockPonyService, in spite of this line: providers: [{provide: PonyService, useClass: MockPonyService}].
The question: How I should mock the service?
It's because of this
#Component({
providers: [PonyService] <======
})
This makes it so that the service is scoped to the component, which means that Angular will create it for each component, and also means that it supercedes any global providers configured at the module level. This includes the mock provider that you configure in the test bed.
To get around this, Angular provides the TestBed.overrideComponent method, which allows us to override things like the #Component.providers and #Component.template.
TestBed.configureTestingModule({
declarations: [PoniesComponent, PonyComponent]
})
.overrideComponent(PoniesComponent, {
set: {
providers: [
{provide: PonyService, useClass: MockPonyService}
]
}
});
Another valid approach is to use tokens and rely on Intefaces instead of base classes or concrete classes, which dinosaurs like me love to do (DIP, DI, and other SOLID Blablahs). And allow your component to have its dependencies injected instead of providing it yourself in your own component.
Your component would not have any provider, it would receive the object as an interface in its constructor during angular's magic dependency injection. See #inject used in the constructor, and see the 'provide' value in providers as a text rather than a class.
So, your component would change to something like:
constructor(#Inject('PonyServiceInterface') private ponyService: IPonyService) {
this.ponies = this.ponyService.getPonies(2); }
In your #Component part, you would remove the provider and add it to a parent component such as "app.component.ts". There you would add a token:
providers: [{provide: 'PonyServiceInterface', useClass: PonyService}]
Your unit test component (the analog to app.component.ts) would have:
providers: [{provide: 'PonyServiceInterface', useClass: MockPonyService}]
So your component doesn't care what the service does, it just uses the interface, injected via the parent component (app.component.ts or your unit test component).
FYI: The #inject approach is not very widely used, and at some point it looks like angular fellows prefer baseclasses to interfaces due to how the underlying javascript works.