Override behavior of a stubbed function using JEST not working as expected - unit-testing

I have a test class that tests behavior of various HTTP methods in a Nest controller class. I am using Jest manual mocks to stub the behavior of various functions in the service class so that I do not have to rely on actual dependencies/services, eg. snowflake. I have a top level jest.mock() defined as follows which initializes the mocked version of the service class instead of the actual service class.The mocked service class is created inside mocks folder adjacent to the actual service class.
I am redefining the behavior of one of the mocked functions in the 'error scenario' describe block as shown in the code snippet below, for testing the error scenario . The test scenario : 'throws an error' is failing as it is still picking up the default mocked behavior. Any pointers or help is appreciated.
In short, I want to be able to define different mocked behavior for a single function of the same mocked class for various test scenarios.
Thanks
jest.mock('#modules/shipment-summary/shipment-summary.service');
describe('ShipmentSummaryController', () => {
let shipmentSummaryController: ShipmentSummaryController;
let shipmentSummaryService: ShipmentSummaryService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [],
controllers: [ShipmentSummaryController],
providers: [ShipmentSummaryService],
}).compile();
shipmentSummaryController = moduleRef.get<ShipmentSummaryController>(
ShipmentSummaryController,
);
shipmentSummaryService = moduleRef.get<ShipmentSummaryService>(
ShipmentSummaryService,
);
jest.clearAllMocks();
});
//All the tests inside this describe block work as expected
describe('valid shipment-mode scenario', () => {
describe('valid shipment modes for tenant', () => {
let modes: ShipmentMode[];
beforeEach(async () => {
modes = await shipmentSummaryController.getAllShipmentModes('256');
});
test('calls the service fn. with the correct arg', () => {
expect(shipmentSummaryService.getAvailableShipmentModes).toBeCalledWith(
'256',
);
});
test('all available shipment modes for 256 are returned', () => {
expect(modes).toEqual(validModeDropdown());
});
});
});
// redefining behavior of getAllshipmentModes() is not working
describe('error scenario', () => {
let modes: ShipmentMode[] = []
beforeEach(async () => {
modes = await shipmentSummaryController.getAllShipmentModes('256');
});
beforeAll(() => {
jest.clearAllMocks();
jest.mock('#modules/shipment-summary/shipment-summary.service.ts', () => {
return {
getAvailableShipmentModes: () => {
throw new Error('Test error');
},
}
});
});
test('throws an error', () => {
expect(() => shipmentSummaryController.getAllShipmentModes('256')).toThrow();
})
})
});
My mocked service class is as follows:
export const ShipmentSummaryService = jest.fn().mockReturnValue({
// Fn. to be mocked differently per test scenario.
getAvailableShipmentModes: jest.fn().mockResolvedValue(validModeDropdown()),
});

There are many ways of accomplishing this. The Nest docs outline a number of them. However, one of my preferred ways, useValue, is not as clear as it could be, so I'll added it here.
This example will also use jest in order to spy on a mock, changing its behavior depending on the test.
Imagine these two simple resources
Injectable();
export class SimpleService {
public sayHello(): string {
return "Hello, world!";
}
}
#Controller()
export class SimpleController {
constructor(
#Inject(SimpleService) private readonly simpleService: SimpleService
) {}
#Get()
public controllerSaysHello(): string {
return this.simpleService.sayHello();
}
}
Your tests could look something like this
describe("SimpleController", () => {
let controller: SimpleController;
const mockReturnValue = "Goodbye, world..",
mockSimpleService: SimpleService = {
sayHello: () => mockReturnValue,
};
beforeEach(() => {
jest.restoreAllMocks();
});
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
SimpleController,
{ provide: SimpleService, useValue: mockSimpleService },
],
}).compile();
controller = module.get(SimpleController);
});
test("default mockSimpleService", () => {
const result = controller.controllerSaysHello();
expect(result).toBe(mockReturnValue);
});
test("spied on mockSimpleService", () => {
const differentReturnValue = "Hallo!";
jest
.spyOn(mockSimpleService, "sayHello")
.mockReturnValue(differentReturnValue);
const result = controller.controllerSaysHello();
expect(result).toBe(differentReturnValue);
});
});

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

vitest failed to mock quasar

I am having vue3 app with vite and vitest and trying to mock the Quasar useQuasar composable which I am using in my custom Composable like:
// useLoginRequestBuilder.ts
import { makeUserAuthentication } from "#/main/factories"
import { useQuasar } from "quasar"
export function useLoginRequestBuilder() {
const $q = useQuasar()
async function login() {
try {
$q.loading.show()
const auth = makeUserAuthentication()
return await auth.signinRedirect()
} catch (e) {
console.log(e)
$q.loading.hide()
$q.notify({
color: "red-4",
textColor: "white",
icon: "o_warning",
message: "Login Failed!",
})
}
}
return {
login,
}
}
and I am trying to mock quasar in tests like:
// useLoginRequestBuilder.spec.ts
import { useLoginRequestBuilder } from "#/main/builders"
vi.mock("quasar", () => ({ // <--- this is not really mocking quasar
useQuasar: () => ({
loading: {
show: () => true,
hide: () => true,
},
}),
}))
const spyAuth = vi.fn(() => Promise.resolve(true))
vi.mock("#/main/factories", () => ({
makeUserAuthentication: () => ({
signinRedirect: () => spyAuth(),
}),
}))
describe("test useLoginRequestBuilder", () => {
test("should call signinRedirect", async () => {
const { login } = useLoginRequestBuilder()
const sut = await login()
expect(sut).toBe(true)
})
})
vi.mock("quasar"... is failing to mock quasar and I am getting below error. That means, it failed to mock and failed to get the $q.loading.... object.
TypeError: Cannot read properties of undefined (reading 'loading')
I understand that there is a separate testing lib for quasar, here but I think this is not really the case here.
Bordering on a necro-post, but I had a similar issue that the mocking factory wasn't creating the plugins being used in non-Vue components, and had to mock each call individually in the end.
Though I'd add it here for anyone else
vitest.mock("quasar", () => vi.fn()); // this doesn't mock out calls
// use individual mocks as below
import { Loading } from "quasar";
vi.spyOn(Loading, "show").mockImplementation(() => vi.fn());
vi.spyOn(Loading, "hide").mockImplementation(() => vi.fn());

How do I unit test a custom CacheInterceptor from NestJS?

I wrote an own CacheInterceptor to cache POST requests as well and take the Accept-Language header into account. Of course I want to unit test it, but I don't know how to properly do so, since the trackBy method needs an ExecutionContext and the method uses the httpAdapterHost and reflector fields.
Has anybody done this before and knows how to achieve full test coverage?
EDIT: Here is the code of the CacheInterceptor
import {
CACHE_KEY_METADATA,
CacheInterceptor,
ExecutionContext,
Injectable,
} from '#nestjs/common';
import { createHash } from 'crypto';
#Injectable()
export class MyCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const httpAdapter = this.httpAdapterHost.httpAdapter;
const cacheMetadata = this.reflector.get(CACHE_KEY_METADATA, context.getHandler());
const request = context.switchToHttp().getRequest();
return [
cacheMetadata,
httpAdapter.getRequestUrl(request),
JSON.stringify(request.body),
request.headers['accept-language'],
]
.reduce(
(hash, somethingToHash) => (
hash.update(
somethingToHash
? Buffer.from(somethingToHash)
: Buffer.alloc(0)
)
),
createHash('md5'),
)
.digest('hex');
}
}
Please bear in mind that the following example is testing the interceptor in isolation. Some tweaks may be needed for your use case, but the overall approach should be valid.
I would inject the cache and reflector dependencies using the constructor:
#Injectable()
export class MyCacheInterceptor extends CacheInterceptor {
constructor(
#Inject(CACHE_MANAGER) protected readonly cacheManager: Cache,
#Inject(Reflector) protected readonly reflector: Reflector
) {
super(cacheManager, reflector);
}
trackBy(context: ExecutionContext): string | undefined {
// ...
// ...
Your tests could look like:
describe("MyCacheInterceptor", () => {
let interceptor: MyCacheInterceptor;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [CacheModule.register()],
providers: [
{ provide: CACHE_MANAGER, useValue: {} },
{ provide: Reflector, useValue: { get: () => "hello" } },
MyCacheInterceptor,
],
}).compile();
// see issue: https://github.com/nestjs/nest/issues/8076
module.createNestApplication();
interceptor = module.get(MyCacheInterceptor);
});
it("creates", () => {
expect(interceptor).toBeTruthy();
});
it("tracks something", () => {
const mockExecutionContext: ExecutionContext = createMock<ExecutionContext>(
{
getHandler: () => ({}),
switchToHttp: () => ({
getRequest: () => ({
url: "/test-url",
originalUrl: "/test-url",
method: "GET",
body: {
someKey: "someValue",
},
headers: {
"accept-language": "en",
},
}),
}),
}
);
const result = interceptor.trackBy(mockExecutionContext);
expect(result).toBe("d4f8ad8ba612cda9a5fda09cc244120c");
});
});
There is a way of mocking httpAdapterHost (as well as cacheManager and reflector):
(interceptor["httpAdapterHost"] as any) = {
httpAdapter: { getRequestUrl: () => "hello" },
};
I consider this an anti-pattern, because you shouldn't be mocking/spying on internal methods and properties. However, if you check this GitHub issue, you'll see that there isn't a good or proper way of mocking an HttpAdapterHost, so in this case it may be a good rule to break.
createMock comes from #golevelup/ts-jest

Testing Observable based call hierarchy in angular

I am trying to test a component which uses Observables and then cascades through several function calls when the Observable resolves. Here is a version of the component.
export class NotificationComponent implements OnInit {
private answerSubscription: Subscription;
constructor(public toasterService: ToasterService, private commentService: CommentService) { }
ngOnInit() {
this.answerSubscription = this.commentService.answer$.subscribe(
answer => this.commentComplete(answer));
}
commentComplete(answer) {
this.toasterService.clear(answer.toastId);
let promptAns = this.search(answer.toastId);
}
}
and here is my test:
class MockToastService {
clear() {}
}
class MockCommentService {
answer$: Observable<any>;
constructor() {
this.answer$ = Observable.of({toastId: '123'});
}
}
describe('NotificationComponent', () => {
let component: NotificationComponent; let fixture: ComponentFixture<NotificationComponent>;
let mockComment = new MockCommentService(); let mockToast = new MockToastService();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NotificationComponent, MockToast],
providers: [{ provide: ToasterService, useValue: mockToast },
{ provide: CommentService, useValue: mockComment }]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NotificationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should complete notification on answer', () => {
spyOn(component, 'commentComplete'); spyOn(mockToast, 'clear');
expect(component.commentComplete).not.toHaveBeenCalled();
component.ngOnInit();
expect(component.commentComplete).toHaveBeenCalled();
expect(mockToast.clear).toHaveBeenCalled();
});
});
The test passes on expect(component.commentComplete).toHaveBeenCalled();, but fails on expect(mockToast.clear).toHaveBeenCalled(). As you can see from the component, toasterService.clear( should be called straight after commentComplete, however, I have stepped through with a debugger, and the test criteria is being checked before the clear function is being called.
I have tried adding fakeAsync and tick(), but am still facing the issue. Any idea how I can make this test's timing work?
You should use fake Async here but as understand there the issues was not with it.
You fake 'commentComplete' function by spyOn(component,'commentComplete') but you need to spy and do its job. change to 'spyOn(component, 'commentComplete').and.callThrough();'
Spies: and.callThrough. By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation.
https://jasmine.github.io/2.0/introduction.html
here is the code that should work:
it('should complete notification on answer', fakeAsync(() => {
const spyComplete = spyOn(component, 'commentComplete').and.callThrough();
const spyToast = spyOn(mockToast, 'clear');
expect(component.commentComplete).not.toHaveBeenCalled();
component.ngOnInit();
tick();
expect(spyComplete).toHaveBeenCalled();
expect(spyToast).toHaveBeenCalled();
}));

Angular 2 testing components with observables

I am testing a angular component and the code is
ngOnInit(): void {
this.getNar();
}
getNar(): void {
let self = this;
this.dashboardService.getNar().subscribe(
res => self.narIds = res.narIds,
error => self.error = error,
function () {
self.narIds.forEach(element => {
// Some Code
});
}
);
}
The Service provider for this i.e Dashboard Service is
getNar(): Observable<any> {
return this.http.get(Config.Api.GetNar + '1/nar').map((res: Response) => res.json());
}
And my Test cases are:
let res = '"narIds":[{"id":1,"narId":"104034-1","narName":"SDLC Platform"},{"id":2,"narId":"64829-1","narName":"EMS-EMS"}]';
describe('Application Health Component', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
providers: [MockBackend, DashboardService],
imports: [ChartsModule, SlimScrollModule, HttpModule],
declarations: [CompletedFilterPipe, ApplicationHealthComponent]
})
.compileComponents()
.then(createComponent);
}));
it('should call the getNar when ngOnInit is called', async(() => {
spyOn(dashboardService, 'getNar').and.returnValue(Observable.of(res));
comp.ngOnInit();
expect(dashboardService.getNar).toHaveBeenCalled();
}));
});
function createComponent() {
fixture = TestBed.createComponent(ApplicationHealthComponent);
comp = fixture.componentInstance;
dashboardService = fixture.debugElement.injector.get(DashboardService);
};
The problem I am getting is the test case gives an error that forEach is undefined.
The error message is not that forEach function is not defined, it's that your object "self.narIds" is undefined. Fairly sure this is due to the way you declared your onComplete function in Observable.subscribe
related to this Rx Subscribe OnComplete fires but cannot use the data
change your
function () {
self.narIds.forEach(element => {
// Some Code
});
code to
() => {
self.narIds.forEach(element => {
// Some Code
});