Unit test say that cannot read property 'map' of undefined - unit-testing

I'm trying to run a unity test on a method by mocking an addressList but it says that it cannot read the property of undefined.
The method:
async paginate(
user: User,
options: IPaginationOptions,
): Promise<Pagination<AddressResponse>> {
const pagination = await this.addressRepository.paginate(user, options);
return new Pagination(
await Promise.all(
pagination.items.map(async (address) => new AddressResponse(address)),
),
pagination.meta,
pagination.links,
);
}
The problem is, when I put a console.log() to read the variable "pagination" it shows me an array that is not empty on the concole:
console.log
addressList: [ Address {} ]
this is what the repository is returning to me.
The test that I'm trying to run is this one:
describe('AddressService', () => {
let addressService: AddressService;
const user = new User();
const addressList: Address[] = [new Address()];
console.log('addressList: ', addressList);
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AddressService,
{
provide: getRepositoryToken(AddressRepository),
useValue: {
paginate: jest.fn().mockResolvedValue(addressList),
create: jest.fn(),
save: jest.fn(),
findById: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();
addressService = module.get<AddressService>(AddressService);
});
it('should be defined', () => {
expect(addressService).toBeDefined();
});
describe('paginate', () => {
it('should return an address list successfully', async () => {
// Act
const result = await addressService.paginate(user, {
page: 1,
limit: 10,
});
// Assert
expect(result).toEqual(addressList);
});
});
});
What is the issue with my code? I'm trying to fix this by days.

Your paginate variable is indeed defined and populated, but the property you first accecss in the code is not. You try to access paginate.items while your mock just returns an array of Address. Change your paginate mock to be paginate: jest.fn().mockResolvedValue({ items: addressList }) and it should be fixed

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),
},
},

Nestjs mongoose unit test: TypeError: <functionName> is not a function

according to this github repo we want to test a user sample
consider this test file :
const mockUser = (
phone = '9189993388',
password = 'jack1234',
id = '3458',
): User => ({
phone,
password,
_id: new Schema.Types.ObjectId(id),
});
const mockUserDoc = (mock?: Partial<User>): Partial<IUserDocument> => ({
phone: mock?.phone || '9189993388',
password: mock?.password || 'jack1234',
_id: mock?._id || new Schema.Types.ObjectId('3458'),
});
const userArray = [
mockUser(),
mockUser('Jack', '9364445566', 'jack#gmail.com'),
];
const userDocArray = [
mockUserDoc(),
mockUserDoc({
phone: '9364445566',
password: 'jack1234',
_id: new Schema.Types.ObjectId('342'),
}),
mockUserDoc({
phone: '9364445567',
password: 'mac$',
_id: new Schema.Types.ObjectId('425'),
}),
];
describe('UserRepository', () => {
let repo: UserRepository;
let model: Model<IUserDocument>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRepository,
{
provide: getModelToken('User'),
// notice that only the functions we call from the model are mocked
useValue: {
new: jest.fn().mockResolvedValue(mockUser()),
constructor: jest.fn().mockResolvedValue(mockUser()),
find: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
create: jest.fn(),
remove: jest.fn(),
exec: jest.fn(),
},
},
],
}).compile();
repo = module.get<UserRepository>(UserRepository);
model = module.get<Model<IUserDocument>>(getModelToken('User'));
});
it('should be defined', () => {
expect(repo).toBeDefined();
});
afterEach(() => {
jest.clearAllMocks();
});
it('should return all users', async () => {
jest.spyOn(model, 'find').mockReturnValue({
exec: jest.fn().mockResolvedValueOnce(userDocArray),
} as any);
const users = await repo.findAll({});
expect(users).toEqual(userArray);
});
it('should getOne by id', async () => {
const userId = '324';
jest.spyOn(model, 'findOne').mockReturnValueOnce(
createMock<Query<IUserDocument, IUserDocument>>({
exec: jest.fn().mockResolvedValueOnce(
mockUserDoc({
_id: new Schema.Types.ObjectId(userId),
}),
),
}) as any,
);
const findMockUser = mockUser('Tom', userId);
const foundUser = await repo.findById(new Schema.Types.ObjectId(userId));
expect(foundUser).toEqual(findMockUser);
});
and this is the user document file:
export interface IUserDocument extends Document {
_id: Schema.Types.ObjectId;
email?: string;
password: string;
firstName?: string;
lastName?: string;
nationalCode?: string;
phone: string;
address?: string;
avatar?: string;
}
the 1th and 2nd test are passed but the third one throws:
TypeError: this.userModel.findById is not a function
also the interface is extended from mongoose Document, the findById function is not recognized in the test.
this is the github repo available.
so any help will be appreciated.
Notice how in your UserModel mock you don't provide a mock function for findById
{
provide: getModelToken('User'),
// notice that only the functions we call from the model are mocked
useValue: {
new: jest.fn().mockResolvedValue(mockUser()),
constructor: jest.fn().mockResolvedValue(mockUser()),
find: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
create: jest.fn(),
remove: jest.fn(),
exec: jest.fn(),
findById: jest.fn(), // <-------------- Add this
},
},
There needs to be a findById method in that set of methods that you mock.
by the way, i needed the constructor to be called via new so i preferd to define a mock class like this:
class UserModelMock {
constructor(private data) {}
new = jest.fn().mockResolvedValue(this.data);
save = jest.fn().mockResolvedValue(this.data);
static find = jest.fn().mockResolvedValue(mockUser());
static create = jest.fn().mockResolvedValue(mockUser());
static remove = jest.fn().mockResolvedValueOnce(true);
static exists = jest.fn().mockResolvedValue(false);
static findOne = jest.fn().mockResolvedValue(mockUser());
static findByIdAndUpdate = jest.fn().mockResolvedValue(mockUser());
static findByIdAndDelete = jest.fn().mockReturnThis();
static exec = jest.fn();
static deleteOne = jest.fn().mockResolvedValue(true);
static findById = jest.fn().mockReturnThis();
}

Vue test utils incorrect value of computed

Hello I have checked the behaviour in application and it works with same data from api as I'm providing in mocked api call Api.contracts.getContractDetails.mockImplementationOnce(() => ({ data })); However, the value of hasWatermark computed is false - while it should be true.
How can I debug this? Is it possible to check computed in tests? This is my test:
function createWrapper() {
const i18n = new VueI18n({
locale: "en",
missing: jest.fn(),
});
return mount(EmployeeContract, {
i18n,
localVue,
store,
mocks: { $route: { query: {}, params: { id: "123" } }, $buefy: { toast: { open: jest.fn() } } },
stubs: ["spinner", "router-link", "b-switch"],
});
it("should add watermark for preview once it has rejected status", async () => {
const data = singleContract;
Api.contracts.getContractDetails.mockImplementationOnce(() => ({ data }));
const wrapper = createWrapper();
await flushPromises();
expect(wrapper.vm.hasWatermark).toBeTruthy();
});

Jest mocking / spying on Mongoose chained (find, sort, limit, skip) methods

What I am looking to do:
spy on the method calls chained onto find() used in a static Model method definition
chained methods: sort(), limit(), skip()
Sample call
goal: to spy on the arguments passed to each of the methods in a static Model method definition:
... static method def
const results = await this.find({}).sort({}).limit().skip();
... static method def
what did find() receive as args: completed with findSpy
what did sort() receive as args: incomplete
what did limit() receive as args: incomplete
what did skip() receive as args: incomplete
What I have tried:
the mockingoose library but it is limited to just find()
I have been able to successfully mock the find() method itself but not the chained calls that come after it
const findSpy = jest.spyOn(models.ModelName, 'find');
researching for mocking chained method calls without success
I was not able to find a solution anywhere. Here is how I ended up solving this. YMMV and if you know of a better way please let me know!
To give some context this is part of a REST implementation of the Medium.com API I am working on as a side project.
How I mocked them
I had each chained method mocked and designed to return the Model mock object itself so that it could access the next method in the chain.
The last method in the chain (skip) was designed to return the result.
In the tests themselves I used the Jest mockImplementation() method to design its behavior for each test
All of these could then be spied on using expect(StoryMock.chainedMethod).toBeCalled[With]()
const StoryMock = {
getLatestStories, // to be tested
addPagination: jest.fn(), // already tested, can mock
find: jest.fn(() => StoryMock),
sort: jest.fn(() => StoryMock),
limit: jest.fn(() => StoryMock),
skip: jest.fn(() => []),
};
Static method definition to be tested
/**
* Gets the latest published stories
* - uses limit, currentPage pagination
* - sorted by descending order of publish date
* #param {object} paginationQuery pagination query string params
* #param {number} paginationQuery.limit [10] pagination limit
* #param {number} paginationQuery.currentPage [0] pagination current page
* #returns {object} { stories, pagination } paginated output using Story.addPagination
*/
async function getLatestStories(paginationQuery) {
const { limit = 10, currentPage = 0 } = paginationQuery;
// limit to max of 20 results per page
const limitBy = Math.min(limit, 20);
const skipBy = limitBy * currentPage;
const latestStories = await this
.find({ published: true, parent: null }) // only published stories
.sort({ publishedAt: -1 }) // publish date descending
.limit(limitBy)
.skip(skipBy);
const stories = await Promise.all(latestStories.map(story => story.toResponseShape()));
return this.addPagination({ output: { stories }, limit: limitBy, currentPage });
}
Full Jest tests to see implementation of the mock
const { mocks } = require('../../../../test-utils');
const { getLatestStories } = require('../story-static-queries');
const StoryMock = {
getLatestStories, // to be tested
addPagination: jest.fn(), // already tested, can mock
find: jest.fn(() => StoryMock),
sort: jest.fn(() => StoryMock),
limit: jest.fn(() => StoryMock),
skip: jest.fn(() => []),
};
const storyInstanceMock = (options) => Object.assign(
mocks.storyMock({ ...options }),
{ toResponseShape() { return this; } }, // already tested, can mock
);
describe('Story static query methods', () => {
describe('getLatestStories(): gets the latest published stories', () => {
const stories = Array(20).fill().map(() => storyInstanceMock({}));
describe('no query pagination params: uses default values for limit and currentPage', () => {
const defaultLimit = 10;
const defaultCurrentPage = 0;
const expectedStories = stories.slice(0, defaultLimit);
// define the return value at end of query chain
StoryMock.skip.mockImplementation(() => expectedStories);
// spy on the Story instance toResponseShape() to ensure it is called
const storyToResponseShapeSpy = jest.spyOn(stories[0], 'toResponseShape');
beforeAll(() => StoryMock.getLatestStories({}));
afterAll(() => jest.clearAllMocks());
test('calls find() for only published stories: { published: true, parent: null }', () => {
expect(StoryMock.find).toHaveBeenCalledWith({ published: true, parent: null });
});
test('calls sort() to sort in descending publishedAt order: { publishedAt: -1 }', () => {
expect(StoryMock.sort).toHaveBeenCalledWith({ publishedAt: -1 });
});
test(`calls limit() using default limit: ${defaultLimit}`, () => {
expect(StoryMock.limit).toHaveBeenCalledWith(defaultLimit);
});
test(`calls skip() using <default limit * default currentPage>: ${defaultLimit * defaultCurrentPage}`, () => {
expect(StoryMock.skip).toHaveBeenCalledWith(defaultLimit * defaultCurrentPage);
});
test('calls toResponseShape() on each Story instance found', () => {
expect(storyToResponseShapeSpy).toHaveBeenCalled();
});
test(`calls static addPagination() method with the first ${defaultLimit} stories result: { output: { stories }, limit: ${defaultLimit}, currentPage: ${defaultCurrentPage} }`, () => {
expect(StoryMock.addPagination).toHaveBeenCalledWith({
output: { stories: expectedStories },
limit: defaultLimit,
currentPage: defaultCurrentPage,
});
});
});
describe('with query pagination params', () => {
afterEach(() => jest.clearAllMocks());
test('executes the previously tested behavior using query param values: { limit: 5, currentPage: 2 }', async () => {
const limit = 5;
const currentPage = 2;
const storyToResponseShapeSpy = jest.spyOn(stories[0], 'toResponseShape');
const expectedStories = stories.slice(0, limit);
StoryMock.skip.mockImplementation(() => expectedStories);
await StoryMock.getLatestStories({ limit, currentPage });
expect(StoryMock.find).toHaveBeenCalledWith({ published: true, parent: null });
expect(StoryMock.sort).toHaveBeenCalledWith({ publishedAt: -1 });
expect(StoryMock.limit).toHaveBeenCalledWith(limit);
expect(StoryMock.skip).toHaveBeenCalledWith(limit * currentPage);
expect(storyToResponseShapeSpy).toHaveBeenCalled();
expect(StoryMock.addPagination).toHaveBeenCalledWith({
limit,
currentPage,
output: { stories: expectedStories },
});
});
test('limit value of 500 passed: enforces maximum value of 20 instead', async () => {
const limit = 500;
const maxLimit = 20;
const currentPage = 2;
StoryMock.skip.mockImplementation(() => stories.slice(0, maxLimit));
await StoryMock.getLatestStories({ limit, currentPage });
expect(StoryMock.limit).toHaveBeenCalledWith(maxLimit);
expect(StoryMock.addPagination).toHaveBeenCalledWith({
limit: maxLimit,
currentPage,
output: { stories: stories.slice(0, maxLimit) },
});
});
});
});
});
jest.spyOn(Post, "find").mockImplementationOnce(() => ({
sort: () => ({
limit: () => [{
id: '613712f7b7025984b080cea9',
text: 'Sample text'
}],
}),
}));
Here is how I did this with sinonjs for the call:
await MyMongooseSchema.find(q).skip(n).limit(m)
It might give you clues to do this with Jest:
sinon.stub(MyMongooseSchema, 'find').returns(
{
skip: (n) => {
return {
limit: (m) => {
return new Promise((
resolve, reject) => {
resolve(searchResults);
});
}
}
}
});
sinon.stub(MyMongooseSchema, 'count').resolves(searchResults.length);
This worked for me:
jest.mock("../../models", () => ({
Action: {
find: jest.fn(),
},
}));
Action.find.mockReturnValueOnce({
readConcern: jest.fn().mockResolvedValueOnce([
{ name: "Action Name" },
]),
});
All the above didn't work in my case, after some trial and error this worked for me:
const findSpy = jest.spyOn(tdataModel.find().sort({ _id: 1 }).skip(0).populate('fields'), 'limit')
NOTE: you need to mock the query, in my case I use NestJs:
I did the following:
find: jest.fn().mockImplementation(() => ({
sort: jest.fn().mockImplementation((...args) => ({
skip: jest.fn().mockImplementation((...arg) => ({
populate: jest.fn().mockImplementation((...arg) => ({
limit: jest.fn().mockImplementation((...arg) => telemetryDataStub),
})),
})),
})),
})),
findOne: jest.fn(),
updateOne: jest.fn(),
deleteOne: jest.fn(),
create: jest.fn(),
count: jest.fn().mockImplementation(() => AllTelemetryDataStub.length),
for me it worked like this:
AnyModel.find = jest.fn().mockImplementationOnce(() => ({
limit: jest.fn().mockImplementationOnce(() => ({
sort: jest.fn().mockResolvedValue(mock)
}))
}))