I have the following async function:
async $onInit() {
try {
this.settings = await this.Service.getSettings(this.companyId);
} catch (error) {
this.uiFeedback.showSystemError('There was an error processing the request.', error);
}
}
I am trying to test if the function displays an error if the async function catches an error. At the moment the only spec which successfully managed to do this was the following:
it('should show error if api call to get availability settings fails', () => {
Company.getSettings = () => {
throw new Error();
};
try {
cmp.$onInit().catch(() => {});
}
catch (error) {
expect(uiFeedback.showSystemError).toHaveBeenCalled();
}
});
So basically i need to add two extra catch blocks within my test to get this test to work. Otherwise i get the following error:
ERROR: 'Unhandled promise rejection', 'error'
Is there a better way to do this?
So it turns out the issue is simply how karma handles errors for async functions. this.uiFeedback was undefined in the $onInit function. However karma did not show the uiFeedback is undefined error until i ran the tests in Chrome and checked the console there. Something for everyone to watch out for when testing async functions with karma jasmine.
Related
This is my controller class:
#Put()
async updateUser(#BodyToClass() user: UpsertUserDto): Promise<UpsertUserDto> {
try {
return await this.userService.updateUser(user);
} catch (e) {
throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is my Usercontroller.spec test class, I am writing the testcases for exception handling for negative cases.
While running getting error on this line:
".then(() => done.fail('Client controller should return INTERNAL_SERVER_ERROR 500 '))
Here is my failure message from Jest:
at Env.fail (../node_modules/jest-jasmine2/build/jasmine/Env.js:722:61) at user/user.controller.spec.ts:51:33 at Object. (user/user.controller.spec.ts:50:15)
I am not sure where I am doing mistake or whether there is other way to handle the exception in nestjs.
it('should throw INTERNAL_SERVER_ERROR if user not update', async (done) => {
const expectedResult = undefined;
const testuser = new UpsertUserDto();
testuser.id = '123';
jest.spyOn(userservice, 'updateUser').mockResolvedValue(expectedResult);
await usercontroller.updateUser(testuser)
.then(() => done.fail('Client controller should return INTERNAL_SERVER_ERROR 500 '))
.catch((error) => {
expect(error.status).toBe(500);
expect(error.message).toMatchObject({error: ' INTERNAL_SERVER_ERROR', statusCode: 500});
done();
});
});
You're using mockResolvedValue in your jest.spyOn which means that in a .then chain, the .catch will never be executed. Looking at your code, all that's happening it your UserService method returns undefined and as there is no logic around it, the controller then returns that undefined meaning there's no error to be caught. If you are trying to test an error, you should be using mockRejectedValue instead. Also, it's usually not the best practice to mix using async/await with using a done callback, as it can lead to some weird situations.
And lastly, Jest has a built in way to check for errors from a function. Your test could look something like this instead:
it('should throw INTERNAL_SERVER_ERROR if user not update', async () => {
const testuser = new UpsertUserDto();
testuser.id = '123';
jest.spyOn(userservice, 'updateUser').mockRejectedValue(new Error('There was an error'));
await expect(usercontroller.updateUser(testuser)).rejects.toThrow(HttpException);
await expect(usercontroller.updateUser(testuser)).rejects.toThrow('There was an error');
});
I suggest you spend some time reading Jest's documentation and possibly looking at some examples to get your feet on the ground with testing.
I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});
I am trying to write a unit test with jest/enzyme that tests if console.error() has been called in the catch() of a try/catch, but trying to do so either results in a successful test when it should be unsuccessful, or an "Expected mock function to have been called, but it was not called" error.
Function to test:
export const playSound = (soundName, extension = 'wav') => {
try {
SoundPlayer.onFinishedPlaying(success => success);
SoundPlayer.playSoundFile(soundName, extension);
} catch (err) {
console.error(`Error playing sound '${soundName}':`, err);
return err;
}
};
So the above takes a single argument soundName, which is a string, and I'm trying to test that a console error is logged when no argument is passed in.
I've most-recently tried the below, which seems to be miles off, and wrongfully returns a passed test.
it('fails to play sound with no method arguments', async () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
try {
playSound();
expect(consoleSpy).not.toHaveBeenCalled();
} catch (err) {
expect(consoleSpy).toHaveBeenCalled();
}
});
Your playSound function will never throw, because you're swallowing the exception.
you simply need this:
it('fails to play sound with no method arguments', async () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
playSound();
expect(consoleSpy).toHaveBeenCalled();
});
you can also check return value of the function call, which will be exception object.
Also if you want to check if function throws you can use
expect(function() { playSound(); }).toThrow();
but this will fail, unless you don't catch the exception or rethrow.
I'm writing an async test that expects the async function to throw like this:
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(await getBadResults()).toThrow()
})
But jest is just failing instead of passing the test:
FAIL src/failing-test.spec.js
● expects to have failed
Failed: I should fail!
If I rewrite the test to looks like this:
expect(async () => {
await failingAsyncTest()
}).toThrow()
I get this error instead of a passing test:
expect(function).toThrow(undefined)
Expected the function to throw an error.
But it didn't throw anything.
You can test your async function like this:
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
'I should fail' string will match any part of the error thrown.
I'd like to just add on to this and say that the function you're testing must throw an actual Error object throw new Error(...). Jest does not seem to recognize if you just throw an expression like throw 'An error occurred!'.
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError("Some error message");
We must wrap the code in a function to catch the error. Here we are expecting the Error message thrown from someAsyncFunction should be equal to "Some error message". We can call the exception handler also
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError(new InvalidArgumentError("Some error message"));
Read more https://jestjs.io/docs/expect#tothrowerror
Custom Error Class
The use of rejects.toThrow will not work for you. Instead, you can combine the rejects method with the toBeInstanceOf matcher to match the custom error that has been thrown.
Example
it("should test async errors", async () => {
await expect(asyncFunctionWithCustomError()).rejects.toBeInstanceOf(
CustomError
)
})
To be able to make many tests conditions without having to resolve the promise every time, this will also work:
it('throws an error when it is not possible to create an user', async () => {
const throwingFunction = () => createUser(createUserPayload)
// This is what prevents the test to succeed when the promise is resolved and not rejected
expect.assertions(3)
await throwingFunction().catch(error => {
expect(error).toBeInstanceOf(Error)
expect(error.message).toMatch(new RegExp('Could not create user'))
expect(error).toMatchObject({
details: new RegExp('Invalid payload provided'),
})
})
})
I've been testing for Firebase cloud functions and this is what I came up with:
test("It should test async on failing cloud functions calls", async () => {
await expect(async ()=> {
await failingCloudFunction(params)
})
.rejects
.toThrow("Invalid type"); // This is the value for my specific error
});
This is built on top of lisandro's answer.
If you want to test that an async function does NOT throw:
it('async function does not throw', async () => {
await expect(hopefullyDoesntThrow()).resolves.not.toThrow();
});
The above test will pass regardless of the value returned, even if undefined.
Keep in mind that if an async function throws an Error, its really coming back as a Promise Rejection in Node, not an error (thats why if you don't have try/catch blocks you will get an UnhandledPromiseRejectionWarning, slightly different than an error). So, like others have said, that is why you use either:
.rejects and .resolves methods, or a
try/catch block within your tests.
Reference:
https://jestjs.io/docs/asynchronous#asyncawait
This worked for me
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(getBadResults()).reject.toMatch('foo')
// or in my case
expect(getBadResults()).reject.toMatchObject({ message: 'foo' })
})
You can do like below if you want to use the try/catch method inside the test case.
test("some test case name with success", async () => {
let response = null;
let failure = null;
// Before calling the method, make sure someAsyncFunction should be succeeded
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toEqual(SOME_MOCK_RESPONSE)
expect(error).toBeNull();
})
test("some test case name with failure", async () => {
let response = null;
let error = null;
// Before calling the method, make sure someAsyncFunction should throw some error by mocking in proper way
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toBeNull();
expect(error).toEqual(YOUR_MOCK_ERROR)
})
Edit:
As my given solution is not taking the advantage of inbuilt jest tests with the throwing feature, please do follow the other solution suggested by #Lisandro https://stackoverflow.com/a/47887098/8988448
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
test("It should test async on failing cloud functions calls", async () => {
failingCloudFunction(params).catch(e => {
expect(e.message).toBe('Invalid type')
})
});
I have the following code in an angularjs controller:
async updateGrid() {
const paging = angular.copy(this.gridData.getServerCallObj());
try {
const model = await this.service.get(paging);
this._setGridData(model, true);
this._$rootScope.$apply();
} catch (e){
this._notification.error(this._$rootScope.lang.notifications.unexpectedError);
}
}
I am trying to test this code through jasmin. The following test is run:
it('updateGrid() should should call broadcast when fail', async () => {
spyOn(service, 'get').and.callFake(() => {
return Promise.reject();
});
spyOn($rootScope, '$broadcast').and.callThrough();
await ctrl.updateGrid();
expect(service.get).toHaveBeenCalled();
expect($rootScope.$broadcast).toHaveBeenCalled();
});
My issue is that jasmine return a result for the test with the message "spec... has not expectations." before the function completes! I can continue debugging, after jasmin says the test is done, at his end! Of course, after its done, the line expect(service.get).toHaveBeenCalled(); throws and error with the message: "expect' was used when there was no current spec, this could be because an asynchronous test timed out".
Does anyone knows what am I doing wrong here?