Jest .toHaveBeenCalled() not recording function calls - jestjs

I'm testing a service in my NestJs application which calls a factory to receive an object with a method up(), which I'm mocking like this:
export const mockExecutorFactory = {
getExecutor: jest.fn().mockImplementation(() => {
return {
up: jest.fn().mockImplementation((_updatedInstance, _parameters) => {
console.log('****************************');
return Promise.resolve({ successful: true });
}),
};
}),
};
In my describe block for the service test I initialize the executor like this:
let executor;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
InstanceService,
{ provide: getRepositoryToken(Instance), useValue: mockInstanceRepository },
ExecutorFactory,
],
})
.overrideProvider(ExecutorFactory)
.useValue(mockExecutorFactory)
.compile();
service = module.get<InstanceService>(InstanceService);
executor = module.get<ExecutorFactory>(ExecutorFactory).getExecutor();
});
And the test its self is written like this:
it('should create a new instance with default settings', async () => {
// Check if instance created correctly
expect(
await service.createInstance(
MOCK_INSTANCE_CREATE_PARAMS.solutionId,
MOCK_INSTANCE_CREATE_PARAMS.instanceType,
MOCK_INSTANCE_CREATE_PARAMS.orgId,
MOCK_INSTANCE_CREATE_PARAMS.parameters,
MOCK_INSTANCE_CREATE_PARAMS.customPrice,
true,
MOCK_INSTANCE_CREATE_PARAMS.req,
),
).toEqual({ instance: MOCK_INSTANCE_DEFAULT });
console.log(executor);
expect(executor.up).toHaveBeenCalled();
});
Essentially, the createInstance() runs through some logic and at the end is supposed to call the executor's up() function.
From the terminal output (photo below) I can see by the console logs that the the up() is being called, but the test fails... Any idea why?

Related

how to mock react-query useQuery in jest

I'm trying to mock out axios that is inside an async function that is being wrapped in useQuery:
import { useQuery, QueryKey } from 'react-query'
export const fetchWithAxios = async () => {
...
...
...
const response = await someAxiosCall()
...
return data
}
export const useFetchWithQuery = () => useQuery(key, fetchWithAxios, {
refetchInterval: false,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: 1,
})
and I want to use moxios
moxios.stubRequest('/some-url', {
status: 200,
response: fakeInputData,
})
useFetchWithQuery()
moxios.wait(function () {
done()
})
but I'm getting all sorts of issues with missing context, store, etc which I'm iterested in mocking out completely.
Don't mock useQuery, mock Axios!
The pattern you should follow in order to test your usages of useQuery should look something like this:
const fetchWithAxios = (axios, ...parameters) => {
const data = axios.someAxiosCall(parameters);
return data;
}
export const useFetchWithQuery = (...parameters) => {
const axios = useAxios();
return useQuery(key, fetchWithAxios(axios, ...parameters), {
// options
})
}
Where does useAxios come from? You need to write a context to pass an axios instance through the application.
This will allow your tests to look something like this in the end:
const { result, waitFor, waitForNextUpdate } = renderHook(() => useFetchWithQuery(..., {
wrapper: makeWrapper(withQueryClient, withAxios(mockedAxios)),
});
await waitFor(() => expect(result.current.isFetching).toBeFalsy());

Create mocked service (object) with one method returning a value

In an Angular environment, how can I very easily create in a Jest environment a mocked service for a service object returning a specific value? This could be via Jest of ng-mocks, etc.
An oversimplified example:
// beforeEach:
// setup an Angular component with a service component myMockService
// Test 1:
// fake "myMockService.doSomething" to return value 10
// expect(myComponent.getBalance()).toEqual( "Your balance: 10");
// Test 2:
// fake "myMockService.doSomething" to return value 20
// expect(myComponent.getBalance()).toEqual( "Your balance: 20");
I have studied the Jest and ng-mocks docs but didn't find a very easy approach. Below you find 2 working approaches. Can you improve the version?
My simplified Angular component:
#Component({
selector: 'app-servicecounter',
templateUrl: './servicecounter.component.html'
})
export class ServicecounterComponent {
private myValue: number = 1;
constructor(private counterService: CounterService) { }
public doSomething(): void {
// ...
myValue = this.counterService.getCount();
}
}
This is the simplified service:
#Injectable()
export class CounterService {
private count = 0;
constructor() { }
public getCount(): Observable<number> {
return this.count;
}
public increment(): void {
this.count++;
}
public decrement(): void {
this.count--;
}
public reset(newCount: number): void {
this.count = newCount;
}
}
Try 1: a working solution: with 'jest.genMockFromModule'.
The disadvantage is that I can only create a returnValue only at the start of each series of tests, so at beforeEach setup time.
beforeEach(async () => {
mockCounterService = jest.genMockFromModule( './counterservice.service');
mockCounterService.getCount = jest.fn( () => 3);
mockCounterService.reset = jest.fn(); // it was called, I had to mock.fn it.
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [ { provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
fixture = TestBed.createComponent(ServicecounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('shows the count', () => {
setFieldValue(fixture, 'reset-input', String(currentCount));
click(fixture, 'reset-button');
expect(mockCounterService.getCount()).toEqual( 3);
expect( mockCounterService.getCount).toBeCalled();
});
Try 2: replace 'jest.genMockFromModule' with 'jest.createMockFromModule': works equally well.
The disadvantage is still that I can create a returnValue only at the start of each series of tests, so at beforeEach setup time.
Try 3: create a mock object upfront: didn't work
jest.mock( "./counterservice.service");
beforeEach(async () => {
// Create fake
mockCounterService = new CounterService();
(mockCounterService.getCount() as jest.Mock).mockReturnValue( 0);
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [{ provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
fixture = TestBed.createComponent(ServicecounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('shows the count', () => {
// do something that will trigger the mockCountService getCount method.
expect(mockCounterService.getCount).toEqual( 0);
});
This doesn't work, giving the error:
> (mockCounterService.getCount() as jest.Mock).mockReturnValue( 0);
> Cannot read property 'mockReturnValue' of undefined
Try 4: with .fn(). The disadvantage is that the original class may change, then the test object MUST change.
beforeEach(async () => {
mockCounterService = {
getCount: jest.fn().mockReturnValue( 0),
increment: jest.fn,
decrement: jest.fn(),
reset: jest.fn
};
await TestBed.configureTestingModule({
declarations: [ServicecounterComponent],
providers: [{ provide: CounterService, useValue: mockCounterService }],
}).compileComponents();
});
it( '... ', () => {
// ...
expect(mockCounterService.reset).toHaveBeenCalled();
});
This time, the error is:
> Matcher error: received value must be a mock or spy function ...
> expect(mockCounterService.reset).toHaveBeenCalled();
Can you help improving this way of working?
You need to use MockBuilder to mock the service, and MockInstance to customize it.
Also getCount is an observable, therefore its mock should return Subject, which we can manipulate.
// to reset MockInstance customizations after tests
MockInstance.scope();
// to use jest.fn on all mocks https://ng-mocks.sudo.eu/extra/auto-spy
beforeEach(() => ngMocks.autoSpy('jest'));
afterEach(() => ngMocks.autoSpy('reset'));
beforeEach(() => MockBuilder(ServicecounterComponent, CounterService));
it('testing', () => {
// this is our control of observable of getCount
const getCount$ = new Subject<number>();
// now we need to return it when getCount is called
const getCount = MockInstance(CounterService, 'getCount', jest.fn())
.mockReturnValue(getCount$);
// now we can use it in our test.
const fixture = MockRender(ServicecounterComponent);
ngMocks.click('.reset-button');
expect(getCount).toHaveBeenCalled();
getCount$.next(3);
expect(ngMocks.formatText(fixture)).toContain('3');
});

Jest with NestJS and async function

I'm trying to a test a async function of a service in nestJS.
this function is async... basically get a value (JSON) from database (using repository - TypeORM), and when successfully get the data, "transform" to a different class (DTO)...
the implementation:
async getAppConfig(): Promise<ConfigAppDto> {
return this.configRepository.findOne({
key: Equal("APPLICATION"),
}).then(config => {
if (config == null) {
return new class implements ConfigAppDto {
clientId = '';
clientSecret = '';
};
}
return JSON.parse(config.value) as ConfigAppDto;
});
}
using a controller, I checked that this worked ok.
Now, I'm trying to use Jest to do the tests, but with no success...
My problem is how to mock the findOne function from repository..
Edit: I'm trying to use #golevelup/nestjs-testing to mock Repository!
I already mocked the repository, but for some reason, the resolve is never called..
describe('getAppConfig', () => {
const repo = createMock<Repository<Config>>();
beforeEach(async () => {
await Test.createTestingModule({
providers: [
ConfigService,
{
provide: getRepositoryToken(Config),
useValue: repo,
}
],
}).compile();
});
it('should return ConfigApp parameters', async () => {
const mockedConfig = new Config('APPLICATION', '{"clientId": "foo","clientSecret": "bar"}');
repo.findOne.mockResolvedValue(mockedConfig);
expect(await repo.findOne()).toEqual(mockedConfig); // ok
const expectedReturn = new class implements ConfigAppDto {
clientId = 'foo';
clientSecret = 'bar';
};
expect(await service.getAppConfig()).toEqual(expectedReturn);
// jest documentation about async -> https://jestjs.io/docs/en/asynchronous
// return expect(service.getAppConfig()).resolves.toBe(expectedReturn);
});
})
the expect(await repo.findOne()).toEqual(mockedConfig); works great;
expect(await service.getAppConfig()).toEqual(expectedReturn); got a timeout => Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout;
using debug, I see that the service.getAppConfig() is called, the repository.findOne() too, but the .then of repository of findOne is never called.
Update: I'm trying to mock the repository using #golevelup/nestjs-testing, and for some reason, the mocked result don't works on service.
If I mock the repository using only jest (like code below), the test works... so, I think my real problem it's #golevelup/nestjs-testing.
...
provide: getRepositoryToken(Config),
useValue: {
find: jest.fn().mockResolvedValue([new Config()])
},
...
So, my real problem is how I'm mocking the Repository on NestJS.
For some reason, when I mock using the #golevelup/nestjs-testing, weird things happens!
I really don't found a good documentation about this on #golevelup/nestjs-testing, so, I gave up using it.
My solution for the question was to use only Jest and NestJS functions... the result code was:
Service:
// i'm injecting Connection because I need for some transactions later;
constructor(#InjectRepository(Config) private readonly configRepo: Repository<Config>, private connection: Connection) {}
async getAppConfig(): Promise<ConfigApp> {
return this.configRepo.findOne({
key: Equal("APPLICATION"),
}).then(config => {
if (config == null) {
return new ConfigApp();
}
return JSON.parse(config.value) as ConfigApp;
})
}
Test:
describe('getAppConfig', () => {
const configApi = new Config();
configApi.key = 'APPLICATION';
configApi.value = '{"clientId": "foo", "clientSecret": "bar"}';
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
ConfigAppService,
{
provide: getRepositoryToken(Config),
useValue: {
findOne: jest.fn().mockResolvedValue(new
Config("APPLICATION", '{"clientId": "foo", "clientSecret": "bar"}')),
},
},
{
provide: getConnectionToken(),
useValue: {},
}
],
}).compile();
service = module.get<ConfigAppService>(ConfigAppService);
});
it('should return ConfigApp parameters', async () => {
const expectedValue: ConfigApp = new ConfigApp("foo", "bar");
return service.getAppConfig().then(value => {
expect(value).toEqual(expectedValue);
})
});
})
some sources utilized for this solution:
https://github.com/jmcdo29/testing-nestjs/tree/master/apps/typeorm-sample
I think expect(await repo.findOne()).toEqual(mockedConfig); works because you mocked it, so it returns right away.
In the case of expect(await service.getAppConfig()).toEqual(expectedReturn);, you did not mock it so it is probably taking more time, thus the it function returns before the Promise resolved completely.
The comments you posted from jest documentation should do the trick if you mock the call to getAppConfig().
service.getAppConfig = jest.fn(() => Promise.resolve(someFakeValue))
or
spyOn(service, 'getAppConfig').and.mockReturnValue(Promise.resolve(fakeValue))
This answer from #roberto-correia made me wonder if there must be something wrong with the way we are using createMock from the package #golevelup/nestjs-testing.
It turns out that the reason why the method exceeds the execution time has to do with the fact that createMock does not implement the mocking, and does not return anything, unless told to do so.
To make the method work, we have to make the mocked methods resolve something at the beginning of the test:
usersRepository.findOneOrFail.mockResolvedValue({ userId: 1, email: "some-random-email#email.com" });
A basic working solution:
describe("UsersService", () => {
let usersService: UsersService;
const usersRepository = createMock<Repository<User>>();
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: usersRepository,
},
}).compile();
usersService = module.get(UsersService);
});
it("should be defined", () => {
expect(usersService).toBeDefined();
});
it("finds a user", async () => {
usersRepository.findOne.mockResolvedValue({ userId: 1, email: "some-random-email#email.com" });
expect(await usersRepository.findOne()).toBe({ userId: 1, email: "some-random-email#email.com" });
});
});

How to mock typeorm connection

In integration tests I am using the following snippets to create connection
import {Connection, createConnection} from 'typeorm';
// #ts-ignore
import options from './../../../ormconfig.js';
export function connectDb() {
let con: Connection;
beforeAll(async () => {
con = await createConnection(options);
});
afterAll(async () => {
await con.close();
});
}
I am trying to unit test a class which calls typeorm repository in one of its method and without call that helper function connectDb() above I get the following error which is expected of course.
ConnectionNotFoundError: Connection "default" was not found.
My question is how can I mock connection. I have tried the following without any success
import typeorm, {createConnection} from 'typeorm';
// #ts-ignore
import options from "./../../../ormconfig.js";
const mockedTypeorm = typeorm as jest.Mocked<typeof typeorm>;
jest.mock('typeorm');
beforeEach(() => {
//mockedTypeorm.createConnection.mockImplementation(() => createConnection(options)); //Failed
mockedTypeorm.createConnection = jest.fn().mockImplementation(() => typeorm.Connection);
MethodRepository.prototype.changeMethod = jest.fn().mockImplementation(() => {
return true;
});
});
Running tests with that kind of mocking gives this error
TypeError: decorator is not a function
Note: if I call connectDb() in tests everything works fine. But I don't want to do that since it takes too much time as some data are inserted into db before running any test.
Some codes have been omitted for simplicity. Any help will be appreciated
After a bunch of research and experiment I've ended up with this solution. I hope it works for someone else who experienced the same issue...
it does not need any DB connection
testing service layer content, not the DB layer itself
test can cover all the case I need to test without hassle, I just need to provide the right output to related typeorm methods.
This is the method I want to test
#Injectable()
export class TemplatesService {
constructor(private readonly templatesRepository: TemplatesRepository) {}
async list(filter: ListTemplatesReqDTO) {
const qb = this.templatesRepository.createQueryBuilder("tl");
const { searchQuery, ...otherFilters } = filter;
if (filter.languages) {
qb.where("tl.language IN (:...languages)");
}
if (filter.templateTypes) {
qb.where("tl.templateType IN (:...templateTypes)");
}
if (searchQuery) {
qb.where("tl.name LIKE :searchQuery", { searchQuery: `%${searchQuery}%` });
}
if (filter.skip) {
qb.skip(filter.skip);
}
if (filter.take) {
qb.take(filter.take);
}
if (filter.sort) {
qb.orderBy(filter.sort, filter.order === "ASC" ? "ASC" : "DESC");
}
return qb.setParameters(otherFilters).getManyAndCount();
}
...
}
This is the test:
import { SinonStub, createSandbox, restore, stub } from "sinon";
import * as typeorm from "typeorm";
describe("TemplatesService", () => {
let service: TemplatesService;
let repo: TemplatesRepository;
const sandbox = createSandbox();
const connectionStub = sandbox.createStubInstance(typeorm.Connection);
const templatesRepoStub = sandbox.createStubInstance(TemplatesRepository);
const queryBuilderStub = sandbox.createStubInstance(typeorm.SelectQueryBuilder);
stub(typeorm, "createConnection").resolves((connectionStub as unknown) as typeorm.Connection);
connectionStub.getCustomRepository
.withArgs(TemplatesRepository)
.returns((templatesRepoStub as unknown) as TemplatesRepository);
beforeAll(async () => {
const builder: TestingModuleBuilder = Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
database: "test",
entities: [Template],
synchronize: true,
dropSchema: true
})
],
providers: [ApiGuard, TemplatesService, TemplatesRepository],
controllers: []
});
const module = await builder.compile();
service = module.get<TemplatesService>(TemplatesService);
repo = module.get<TemplatesRepository>(TemplatesRepository);
});
beforeEach(async () => {
// do something
});
afterEach(() => {
sandbox.restore();
restore();
});
it("Service should be defined", () => {
expect(service).toBeDefined();
});
describe("list", () => {
let fakeCreateQueryBuilder;
it("should return records", async () => {
stub(queryBuilderStub, "skip" as any).returnsThis();
stub(queryBuilderStub, "take" as any).returnsThis();
stub(queryBuilderStub, "sort" as any).returnsThis();
stub(queryBuilderStub, "setParameters" as any).returnsThis();
stub(queryBuilderStub, "getManyAndCount" as any).resolves([
templatesRepoMocksListSuccess,
templatesRepoMocksListSuccess.length
]);
fakeCreateQueryBuilder = stub(repo, "createQueryBuilder" as any).returns(queryBuilderStub);
const [items, totalCount] = await service.list({});
expect(fakeCreateQueryBuilder.calledOnce).toBe(true);
expect(fakeCreateQueryBuilder.calledOnce).toBe(true);
expect(items.length).toBeGreaterThan(0);
expect(totalCount).toBeGreaterThan(0);
});
});
});
cheers!

How to mock getMongoRepository in service nestjs

I write unit test for my service in nestjs. In my function delete i use getMongoRepository to delete. But i stuck in write the unit test
I've tried write the mock but it's not work
my service
async delete(systemId: string): Promise<DeleteWriteOpResultObject> {
const systemRepository = getMongoRepository(Systems);
return await systemRepository.deleteOne({ systemId });
}
my mock
import { Mock } from './mock.type';
import { Repository, getMongoRepository } from 'typeorm';
// #ts-ignore
export const mockRepositoryFactory: () => Mock<Repository<any>> = jest.fn(
() => ({
save: jest.fn(Systems => Systems),
delete: jest.fn(Systems => Systems),
deleteOne: jest.fn(Systems => Systems),
}),
);
my test
import { ExternalSystemService } from '../external-system.service';
import { Systems } from '../entities/external-system.entity';
module = await Test.createTestingModule({
providers: [
ExternalSystemService,
{
provide: getRepositoryToken(Systems),
useFactory: mockRepositoryFactory,
},
],
}).compile();
service = module.get<ExternalSystemService>(ExternalSystemService);
mockRepository = module.get(getRepositoryToken(Systems));
describe('delete', () => {
it('should delete the system', async () => {
mockRepository.delete.mockReturnValue(undefined);
const deletedSystem = await service.delete(systemOne.systemId);
expect(mockRepository.delete).toBeCalledWith({ systemId: systemOne.systemId });
expect(deletedSystem).toBe(Object);
});
I got this error
ExternalSystemService › delete › should not delete the system
ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (error/ConnectionNotFoundError.ts:8:9)
at ConnectionManager.Object.<anonymous>.ConnectionManager.get (connection/ConnectionManager.ts:40:19)
at Object.getMongoRepository (index.ts:300:35)
at Object.<anonymous> (external-system/tests/external-system.service.spec.ts:176:33)
at external-system/tests/external-system.service.spec.ts:7:71
at Object.<anonymous>.__awaiter (external-system/tests/external-system.service.spec.ts:3:12)
at Object.<anonymous> (external-system/tests/external-system.service.spec.ts:175:51)
You should avoid using global functions and instead use the dependency injection system; this makes testing much easier and is one of the main features of nest.
The nest typeorm module already provides a convenient way of injecting a repository:
1) Inject the repository in your service's constructor:
constructor(
#InjectRepository(Systems)
private readonly systemsRepository: MongoRepository<Systems>,
) {}
2) Use the injected repository
async delete(systemId: string): Promise<DeleteWriteOpResultObject> {
return this.systemsRepository.deleteOne({ systemId });
}
Now your mocked repository will be used in your test.

Resources