I can't figure out why my jest mocks aren't working consistently. I understand the behavior about mocks being lifted, but I don't see how/why that would break my code in this case.
The following works
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();
fetchMock.dontMock();
fetchMock.doMockIf('...', async () => {
return JSON.stringify([
{
AccountName: '...'
}
]);
});
describe('....', () => {
let sut = ....
....
and my fetch call is mocked
If I do the following, my mock fetch is never called
import './mocks';
describe('....', () => {
let sut = ....
....
mocks.ts:
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();
fetchMock.dontMock();
fetchMock.doMockIf('...', async () => {
return JSON.stringify([
{
AccountName: '...'
}
]);
});
Related
I'm trying to test my controller function which is:
import { InstalledPackages } from '../parser/parser.service';
import {
getOutdatedPackages,
InstalledPackageStatus,
} from './version-control.service';
interface OutdatedPackages {
dependencies: InstalledPackageStatus[];
devDependencies: InstalledPackageStatus[];
}
export async function getPackagesUpdatesToNotify(
packages: InstalledPackages,
type = 'package.json',
): Promise<OutdatedPackages> {
return getOutdatedPackages(packages.dependencies, type);
}
And having my service like this:
import { fetch } from "../common/http.service";
export async function getLastPackageVersion(
packageName: string
): Promise<VersionType> {
const url = `https://registry.npmjs.org/-/package/${packageName}/dist-tags`;
return await (<Promise<VersionType>>fetch(url));
}
export async function getOutdatedPackages(
installedPackages: PackagesVersion,
type: string
): Promise<InstalledPackageStatus[]> {
return Promise.all(
Object.keys(installedPackages).map(async (packageName) =>
getLastPackageVersion(packageName)
)
);
}
I've already tried both solutions:
import * as myService from './my.service';
it('my test', async () => {
const getLastPackageVersionSpy = jest.spyOn(myService, 'getLastPackageVersion').mockReturnValue(
Promise.resolve(42),
await getPackagesUpdatesToNotify(packages, type)
});
and
import { getLastPackageVersion } from './my.service';
import { getPackagesUpdatesToNotify } from './version-control.controller';
jest.mock('./myse.service', () => ({
...jest.requireActual('./myse.service'),
getLastPackageVersion: jest.fn(),
}));
it('my test', async () => {
(getLastPackageVersion as jest.Mock).mockResolvedValue(
Promise.resolve(42),
);
await getPackagesUpdatesToNotify(packages, type)
});
But the original function is always called instead of the mocked one.
How to mock the getLastPackageVersion method.
I'm trying to avoid using tools like rewire if possible.
Thank you
Move the getLastPackageVersion to different file, import it in the my.service and then mock it.
my.service:
import { fetch } from "../common/http.service";
import { getLastPackageVersion } from '../last-package-version';
export async function getOutdatedPackages(
installedPackages: PackagesVersion,
type: string
): Promise<InstalledPackageStatus[]> {
return Promise.all(
Object.keys(installedPackages).map(async (packageName) =>
getLastPackageVersion(packageName)
)
);
}
import * as lastPackageVersion from '../last-package-version';
it('my test', async () => {
const getLastPackageVersionSpy = jest.spyOn(lastPackageVersion, 'getLastPackageVersion').mockResolvedValue(42);
await getPackagesUpdatesToNotify(packages, type)
});
getOutdatedPackages is in the same file as the getLastPackageVersion so it cannot be mocked. In your case the getOutdatedPackages is still using the original getLastPackageVersion method.
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());
I have a logger file as below which implements logging functionality. uuidLogger.js
const winston = require('winston'),
CustomTransport = require('./customTransport');
function getLogger(route) {
return winston.createLogger({
defaultMeta: { route },
transports: [new CustomTransport()]
});
}
module.exports = getLogger;
It is imported by a function like this and used for logging testfn.js
const uuidLogger = require('./uuidLogger')('test-fn');
function testMock() {
uuidLogger.info('Hey I am just logging');
}
module.exports = { testMock };
I am trying to mock uuidlogger in testfn.js so that I can track various methods called on uuidLogger object. I tried below approach.
import { testMock } from './testfn';
import getLogger from './uuidLogger';
const logger = getLogger('testfn');
jest.mock('./uuidLogger', () =>
jest.fn(() => ({
info: jest.fn(() => console.log('Mocked function actually called'))
}))
);
it('verify that info method was called on returned object', () => {
testMock('abx');
expect(logger.info).toBeCalledTimes(1);
});
It was able to mock the method called however mock information is not getting reflected in logger.info object.
I also tried below approach
import { testMock } from './testfn';
import getLogger from './uuidLogger';
jest.mock('./uuidLogger', () =>
jest.fn(() => ({ info: jest.fn(() => console.log('Top level fn')) }))
);
const logger = {
error: jest.fn(),
info: jest.fn(() => {
console.log('Overwritten fn');
})
};
getLogger.mockReturnValue(logger);
it('shud return Winston instance', () => {
testMock('abx');
expect(logger.info).toBeCalledTimes(1);
});
Any help on how to get it will be appreciated. Thanks in advance.
It seems to be the assertion is not done on proper variable.
Need to assert on getLogger
Your first approach of writing test case is proper.
Add assertion something like this:
expect(getLogger.mock.results[0].value.info).toBeCalledTimes(1);
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!
I would like to test getFund() method from my service. I use NestJS that uses jest by default.
I have no idea how to test this line with jest: return await this.fundModel.findById(id);. Any idea?
import { Injectable } from '#nestjs/common';
import { Model } from 'mongoose';
import { Fund } from '../../funds/interfaces/fund.interface';
import { InjectModel } from '#nestjs/mongoose';
#Injectable()
export class FundService {
constructor(
#InjectModel('Fund')
private readonly fundModel: Model<Fund>,
) {}
/*****
SOME MORE CODE
****/
async getFund(id: string): Promise<Fund> {
return await this.fundModel.findById(id);
}
}
Edit
Thanks to slideshowp2 answer, I wrote this test.
describe('#getFund', () => {
it('should return a Promise of Fund', async () => {
let spy = jest.spyOn(service, 'getFund').mockImplementation(async () => {
return await Promise.resolve(FundMock as Fund);
});
service.getFund('');
expect(service.getFund).toHaveBeenCalled();
expect(await service.getFund('')).toEqual(FundMock);
spy.mockRestore();
});
});
The problem is that I get this result in my coverage report:
When I hover the line I get statement not covered.
There is only one statement return await this.fundModel.findById(id); in your getFund method. There is no other code logic which means the unit test you can do is only mock this.fundModel.findById(id) method and test
it .toBeCalledWith(someId).
We should mock each method and test the code logic in your getFund method. For now, there is no other code logic.
For example
async getFund(id: string): Promise<Fund> {
// we should mock this, because we should make an isolate environment for testing `getFund`
const fundModel = await this.fundModel.findById(id);
// Below branch we should test based on your mock value: fundModel
if(fundModel) {
return true
}
return false
}
Update
For example:
describe('#findById', () => {
it('should find ad subscription by id correctly', async () => {
(mockOpts.adSubscriptionDataSource.findById as jestMock).mockResolvedValueOnce({ adSubscriptionId: 1 });
const actualValue = await adSubscriptionService.findById(1);
expect(actualValue).toEqual({ adSubscriptionId: 1 });
expect(mockOpts.adSubscriptionDataSource.findById).toBeCalledWith(1);
});
});
The test coverage report: