I want to verify/assert the results of spied function. I'm using nestjs framework with jasmine. I create a jasmine spy on a method i want to "spy" on, that is, eavesdrop args and response/exception. However, I can't access return value of spied method.
Let's say I have an emitter and listener and I want to assert that my listener throws an exception when a DB operation fails.
Listener:
onModuleInit() {
this.emitter.on('documentDeleted', d => this.onDocumentDeleted(d));
}
#CatchAndLogAnyException()
private async onDocumentDeleted(dto: DocumentDeletedEventDTO) {
this.logger.log(`Deleting document with id '${dto.id}'...`);
const result = await this.ResearchHearingTestModel.deleteOne({ _id: dto.id });
if (!result.ok) {
throw new DataAccessException(
`Deleting document with id '${dto.id}' failed. Model.deleteOne(id) result: ${result}`,
);
}
if (result.n < 1) {
throw new DocumentNotFoundException(`Deleting document with id '${dto.id}' failed.`);
}
this.logger.log(`Deleted document with id '${dto.id}.`);
}
Test:
const mockId = 123;
const spyDelete = spyOn(model, 'deleteOne').and.returnValue({ ok: 1, n: 0 });
const spyOnDeleted = spyOn(listener, 'onDocumentDeleted');
spyOnDeleted.and.callThrough();
await emitter.emit('documentDeleted', new DocumentDeletedEventDTO(mockId));
expect(spyOnDeleted).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
expect(spyOnDeleted).toThrow(DocumentNotFoundException);
So when debugging, I can see spyOnDeleted["[[Scopes]]"][0].spy.calls.mostRecent["[[Scopes]]"][0].calls[0].returnValue is a promise i'm probably looking for, but I can't access it or verify on it.
And when I run the test, this is the output:
expect(received).toThrow(expected)
Expected name: "DocumentNotFoundException"
Received function did not throw
95 | expect(spyDelete).toHaveBeenCalledTimes(1);
96 | expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
> 97 | expect(spyOnDeleted).toThrow(DocumentNotFoundException);
| ^
98 | });
99 | });
100 | });
I've seen CallThrough injected spy and several other questions that are similar, but I'm still hoping it's possible to spy on callThrough methods and eavesdrop on in/out of it. Any suggestions?
toThrow cannot be used on spies. You can use spies to mock behavior or use the actual behavior with callThrough and then make sure the method was called with specific parameters. But a spy will not have information about the result it produced (value or error) so you cannot set expectations on it.
If you want to test the behavior of onDocumentDeleted you have to either test it indirectly by observing the effects of the method. In your case (with #CatchAndLogAnyException), it seems to write to the log!? So you can spy on the log and expect it to be called with the error message. Or alternatively, you test the method directly by making it public.
Related
I am trying to add unit tests for my Nest app that uses typeorm. My user service has a mock repository set up like this:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
This works fine as a simple test to see if my service can return a user based on an ID passed in to it. The problem is that I use the findOneOrFail method in other endpoints like save like this:
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
Since the save returns the findOneOrFail method, if I want to test this save method it will always return the value that I set in the mock repository above so I can't test if it will return a new save value or if it returns a new updated value. Am I supposed to make the mock findOneOrFail more generic to handle if it's used in multiple endpoints?
You are creating the mock in your code here:
const mockUsersRepository = {
create: jest.fn().mockImplementation((dto) => dto),
insert: jest.fn((user) => Promise.resolve({ id: 1, ...user })),
findOneOrFail: jest.fn(({ where: { id } }) =>
Promise.resolve({
id: id,
firstName: 'John',
lastName: 'Smith',
created: '2022-08-18T04:43:26.035Z',
}),
)}
The Promise.resolve is what is returning the data for you. I do this a little different, and use the #golevelup/ts-jest library for easy mocking. I highly recommend this for this type of testing.
import { createMock } from '#golevelup/ts-jest'; // Using this to very easily mock the Repository
The beforeEach to create your mock
beforeEach(async () => {
...
const mockUsersRepository = createMock<usersRepository>(); // Create the full mock of the TypeORM Repository
...
In the actual test then, the simple negative test for an exception being thrown:
it('should error out if ...', async () => {
jest.spyOn(mockUsersRepository, 'create').mockImplementation( () => {
throw new Error('Mock error'); // Just error out, contents are not important
});
expect.assertions(3); // Ensure there are 3 expect assertions to ensure the try/catch does not allow code to to bypass and be OK without an exception raised
try {
await service.save(...);
} catch (Exception) {
expect(Exception).toBeDefined();
expect(Exception instanceof HttpException).toBe(true);
expect(Exception.message).toBe('Mock error');
}
});
});
In another test, that requires two steps, you can use the mockResolvedValueOnce() multiple times. For instance, maybe the call to the actual database function needs to read twice, once that it does not exist, and then once that it does. This next test covers that the code being called will do 2 findAll calls.
mockResolvedValueOnce('A').mockResolvedValueOnce('B').mockResolvedValueOnce('C') will return the 'A' for the first call, 'B' for the second, and 'C' for the 3rd and any additional calls. You can also chain mockRejectValueOnce() and others.
In your save function, what I have found is you should simply test the save() function, and have the usersRepository mocked. This way you only need to test the return from this function, without all the work against a data source. Therefore, the mock is on the save() and you simply return the instance of the User that you want.
async save(createUserDto: CreateUserDto): Promise<User> {
const newUser = this.usersRepository.create(createUserDto);
const insertedRecord = await this.usersRepository.insert(newUser);
return this.findOneOrFail(insertedRecord.identifiers[0].id);
}
However, if you want to mock just the storage calls, then the usersRepository is what you need to mock. Then you need to create the mockResolvedValueOnce (or mockResolvedValue() for all calls), against the .create() method, something against the insert() method, and then something against the findOneOrFail() method. All of these are called, so you mock needs to handle all 3.
In these examples, the mockResolvedValueOnce() is simply declaring the return, without having to do the implementation you have done via jest.fn, and mockImplementation.
I have a controller like this
async create(#Body() dto: myDTO,#Req() request, #I18n() i18n?: I18nContext): Promise<MyEntity>
{
return this.Myservice.create(dto, request, i18n);
}
and service like this:
async create(dto: CreateApplicationDTO, request?, i18n?): Promise<Application> {
const MyEntity= this.repository.create(dto);
request.message = i18n.t('common.added_successful');
return this.repository.save(MyEntity);
}
and service.spec file this :
it('check number of created applications', async () => {
await service.create(myDto);
expect(await service.findAll()).toHaveLength(1);
});
When I start test show me this error
TypeError: Cannot set properties of undefined (setting 'message')
41 | return this.repository.save(newApplication);
42 | } catch (e) {
> 43 | return request.message = e.message;
| ^
44 | }
45 |
46 | }
I know I should send request and i18n to service but I don't know how mock them and send them to service in my spec file
You've set request to be optional but set a property on it regardless if it is populated or not. You need to make sure that request has a value before you try to assign to it. For simplicity sake, because you don't give it a type, it could just be {} and then {}.message = will be valid.
As for i18n in your create method you use the object method t so you need the mock to have a t method. Something like { t: jest.fn(() => 'some value') } should be enough to get the test going with a mock.
Having stronger typings will help you though, even if it means it is harder to make mocks. You could use a tool like #golevelup/ts-jest can be used to create full mocks off of types alone
I am trying out NestJS for the first time.
The question is simple, when I DO NOT use async await in my controller, I am able to return the data without await as async/await is used in the repository class methods
#Get('/:id')
getMessage(#Param('id') id: string) {
const messageId = id;
// works just fine 👇
const message = this.messagesService.findOne(messageId);
return message;
}
But when I make use of NotFoundException from NEST to make sure if I found the data I am supposed to return, I am forced to use async/await because without it, it considers the message to be always there. Which I am assuming is a Promise.
#Get('/:id')
async getMessage(#Param('id') id: string) {
const messageId = id;
// 👇 await
const message = await this.messagesService.findOne(messageId);
if (!message) {
throw new NotFoundException('Message with ID not found');
}
return message;
}
And if I do not use await, it does not throw an exception.
The question is, why/how does it work in the first example without the use of await
The await keyword returns a Promise. Therefore if you return a Promise you have satisfied the contract of returning a Promise.
I presume that Nest.js repository methods need to return a Promise. You have two choices. Either use the async keyword or return a Promise. In the first example you have returned a Promise so that is why it works.
Note that you don't need to use async if you don't want to. You can always go old school. This is what your first example would be like with the logic to check the message:
#Get('/:id')
getMessage(#Param('id') id: string) {
const messageId = id;
// works just fine 👇
const promise = this.messagesService.findOne(messageId).then((message) => {
if (!message) {
throw new NotFoundException('Message with ID not found');
}
else {
return message;
}
});
return promise;
}
We know that in JS, program runs synchronously and findOne is a webAPI provided by the browser. As it is a webapi it will first send the line
const message = this.messagesService.findOne(messageId);
to the api and will return that function again to stack once all the data is received.
(Assuming you know how the event loop and api works in JS)
In the first function you are not checking the variable message(if it is true or false) you are just returning the value so it will return only if the value is present.
But in second function you are checking the var message, if that line is written without the await it will directly go to the "if" statement even before the data is received from the api(findOne) at that time message var would still be undefined. So once you write await, stack will not go to the next line unless the answer from api is received, and at time your if statement will be checked perfectly.
The answer to your question is in the architecture of nestjs/node itself. If you return a promise (your first case) it resolve it and then returns the value. To get more clear idea about this check jiripospisil on 3 Aug 2018 on this issue.
I am new to react-testing-library and I have been trying to test one function for a long time.
for example, I want to check if when a button is clicked a given function is called and it's throwing errors. so any help would be highly appreciated and if possible share with me any helpful resources.
signin.js
export default class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleClose = (event, reason) => { };
validate = () => { };
change = (e) => { };
onSubmit = (e) => { };
render() {
return (<div>...</div>);
}
}
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/views/SignIn.js
this is my test
it('should submit form', async () => {
const { getByLabelText, getByText, container, debug } = render(<SignIn />);
const change = jest.fn();
const onSubmit = jest.fn();
const email = getByLabelText('email');
const password = getByLabelText('password');
const submit = getByLabelText('submit');
userEvent.type(email, 'octopusbn#gmail.com');
expect(email.value).toBe('octopusbn#gmail.com');
expect(password.value).toBe('');
expect(change).toHaveBeenCalled();
console.log(password)
await userEvent.click(submit);
expect(onSubmit).toHaveBeenCalled();
});
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/test/signin.test.js
results
> Expected number of calls: >= 1
> Received number of calls: 0
please let know what I am doing wrong.
Full code on GitHub: https://github.com/blaise82/react-testing-library-try
You can test a function by mocking all that is coming from outside of the component (aka dependencies) like - a prop callback, an external library api etc.
Before starting, let's go through what all functions are in the component.
Going through the component, I can list them as below:
Event handlers on elements [like handleClose, onSubmit, change in the component]
Functions internal to the component which do not interact with the state/functions outside the component [validate]
prop functions/library apis being called [axios.post]
Let's discuss them one by one --
Event handlers &
Functions internal to component not interacting with state/functions outside of the component
==> Event handlers that are attached to elements can safely be expected to get called. You don't need to test them if they are called. Rather, what you should test is the after-effect of them being called. Also the functions like validate
Let's take example of the change function that you are trying to test. This function after being called sets the state and the state gets reflected into the form elements. We can assert values of the form elements with a helper like this.
prop functions/library apis being called [axios.post]
==> These functions can be mocked and tested for the number of calls/parameters they are called with.
https://jestjs.io/docs/en/mock-functions.html#mocking-modules
In addition to the snippet of mocking jest as given in the link above, in your case -
axios.post.toHaveBeenCalledWith(expectedParms);
Also you can make it return results/errors you want and test respective component behaviour.
Hope you find this helpful. Cheers!
I think this is because you are not actually passing in your mocked functions to the component. You're just instantiating two constants that happen to have the name of the functions you're trying to watch, but are not actually being used anywhere in your component.
It sounds like you want to spy on your component's internal functions to see that they've been called.
Here's something of an example (not tested) based on a post (linked below) that might help you.
describe('spying on "onSubmit" method', () => {
it('should call onSubmit when the button is clicked', () => {
const wrapper = shallow(<SignIn />);
const instance = wrapper.instance();
jest.spyOn(instance, 'onSubmit');
wrapper.find('button').simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
});
});
Post: https://bambielli.com/til/2018-03-04-directly-test-react-component-methods/#spying-on-incrementcounter
I'm using Mockingoose to mock my mongoose calls when running tests with Jest. I tried this but I get an error
mockingoose.Account.toReturn(
["593cebebebe11c1b06efff0372","593cebebebe11c1b06efff0373"],
"distinct"
);
Error:
ObjectParameterError: Parameter "obj" to Document() must be an object, got 593cebebebe11c1b06efff0372
So then I try passing it an array of document objects but it just returns the documents. How di I get it to return just an array or strings?
Here's the code inside the function I'm testing:
const accountIDs = await Account.find({
userID: "test",
lastLoginAttemptSuccessful: true
}).distinct("_id");
I'm open to other ways of mocking my mongoose calls if someone knows of a better way. Thanks!
You can't.
My bad. I looked into mockingoose implementation and realized, it kind of "supports" distinct by implementing a mock, but it actually returned just the given documents, as for the other operations.
Opened a pull request for this issue and added a test, so you're example should be valid and working.
I think the answer is to not use mockingoose. You can do it pretty easily with jest alone.
You can use jest.spyOn() and then mockImplementation() to mock the first call like find() and update(). Here's an example of findOneAndUpdate() where we're checking to make sure the correct object is passed:
// TESTING:
// await Timeline.findOneAndUpdate(query, obj);
//
const Timeline = require("./models/user.timeline");
...
const TimelineFindOneAndUpdateMock = jest.spyOn(Timeline, "findOneAndUpdate");
const TimelineFindOneAndUpdate = jest.fn((query, obj) => {
expect(obj.sendDateHasPassed).toBeFalsy();
expect(moment(obj.sendDate).format()).toBe(moment("2018-11-05T23:00:00.000Z").format());
});
TimelineFindOneAndUpdateMock.mockImplementation(TimelineFindOneAndUpdate);
If you want to mock a chained function you can have it return an object with the next chained function you want to call. Here's an example of how to mock a chained distinct() call.
// TESTING:
// let accountIDs = await Account.find(query).distinct("_id");
//
// WILL RETURN:
// ["124512341234","124512341234","124512341234"]
//
const Account = require("./models/user.account");
...
const AccountFindMock = jest.spyOn(Account, "find");
const AccountFindDistinctResult = ["124512341234","124512341234","124512341234"];
const AccountFindDistinctResult = jest.fn(() => AccountFindDistinctResult);
const AccountFindResult = {
distinct: AccountFindDistinct
};
const AccountFind = jest.fn(() => AccountFindResult);
AccountFindMock.mockImplementation(AccountFind);
And after your test runs, if you want to check how many times a function is called like how many times distinct() was called you can add this:
expect(AccountFindDistinct).toHaveBeenCalledTimes(0);