Mocking different log levels on Azure Function context with jest in javascript - javascript

I have been playing around with testing my Azure Functions, but I am unable to mock the context log function.
For example I have the following Azure Function:
module.exports = async function (context, req) {
if (req.query.isGood) {
context.log("Goooood!!!")
context.res = {
body: {
message: "This is good!"
}
};
} else {
context.log.error("Not gooood!!!")
context.res = {
status: 404,
body: {
message: "This is not good!"
}
};
}
}
So I want to check the amount of times a certain log occured, for example 'log.error' occured once and 'log' occured twice, but I am unable to mock this.
I tried a couple of combinations like:
log: {
"": jest.fn(),
"error": jest.fn()
}
At this point I'm clueless on how to mock these functions, and am wondering if it is even possible? And how do you create these kind of functions?

In order to do this you need to create a closure function that is immediately invoked. Inside that function, create your default and then add to it the additional methods. In Typescript, you need to cast jest.fn() to the any type to get around type checking.
log: (function() {
let main = <any>jest.fn((message) => message) ;
let info = jest.fn((message) => message);
main.info = info;
return main;
})()
Once you are back in your test, this should then behave as expected:
test ('log test', () => {
context.log("foo");
context.log.info("bar");
expect(context.log.mock.calls[0][0]).toEqual("foo");
expect(context.log.info.mock.calls[0][0]).toEqual("bar");
});

Related

What is considered the correct way to test methods that return http observables?

I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.

Is there a way to pass parameters to a mock function?

using jest to unit test, I have the following line:
jest.mock('../../requestBuilder');
and in my folder, i have a
__mocks__
subfolder where my mock requestBuilder.js is. My jest unit test correctly calls my mock requestBuilder.js correctly. Issue is, my requestBuilder is mocking an ajax return, so I want to be able to determine if I should pass back either a successful or failure server response. Ideally I want to pass a parameter into my mock function to determine if "ajaxSuccess: true/false". How can I do this? Thank you
You don't want to pass a parameter into your mock function, the parameters that are passed to your mock function should be controlled by the piece of code that you are testing. What you want to do is change the mocking behavior between executions of the mock function.
Let's assume that you're trying to test this snippet of code:
// getStatus.js
const requestBuilder = require('./requestBuilder');
module.exports = () => {
try {
const req = requestBuilder('http://fake.com/status').build();
if (req.ajaxSuccess) {
return {status: 'success'};
} else {
return {status: 'failure'}
}
} catch (e) {
return {status: 'unknown'};
}
};
We want to test that getStatus uses the requestBuilder properly, not that the builder.build() method works correctly. Verifying builder.build() is the responsibility of a separate unit test. So we create a mock for our requestBuilder as follows:
// __mocks__/requestBuilder.js
module.exports = jest.fn();
This mock simply sets up the mock function, but it does not implement the behavior. The behavior of the mock should defined in the test. This will give you find grained control of the mocking behavior on a test-by-test basis, rather than attempting to implement a mock that supports every use case (e.g. some special parameter that controls the mocking behavior).
Let's implement some tests using this new mock:
// getStatus.spec.js
jest.mock('./requestBuilder');
const requestBuilder = require('./requestBuilder');
const getStatus = require('./getStatus');
describe('get status', () => {
// Set up a mock builder before each test is run
let builder;
beforeEach(() => {
builder = {
addParam: jest.fn(),
build: jest.fn()
};
requestBuilder.mockReturnValue(builder);
});
// every code path for get status calls request builder with a hard coded URL,
// lets create an assertion for this method call that runs after each test execution.
afterEach(() => {
expect(requestBuilder).toHaveBeenCalledWith('http://fake.com/status');
});
it('when request builder creation throws error', () => {
// Override the mocking behavior to throw an error
requestBuilder.mockImplementation(() => {
throw new Error('create error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).not.toHaveBeenCalled();
});
it('when build throws an error', () => {
// Set the mocking behavior to throw an error
builder.build.mockImplementation(() => {
throw new Error('build error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns success', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: true});
expect(getStatus()).toEqual({status: 'success'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns failure', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: false});
expect(getStatus()).toEqual({status: 'failure'});
expect(builder.build).toHaveBeenCalled();
});
});

sinon stub not restoring properly if the stubbing method is deconstructed

Given the test codes below:
subAuthCall:
const sinon = require('sinon')
const sandbox = sinon.createSandbox()
function stubSuccessCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: true});
}
function stubFailedCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: false});
function restoreSub() {
sandbox.restore();
}
in the authorization.js file, I have:
function authorize(data) {
return data.enabled;
}
module.exports = {
authorize
}
and then in the middleware, I have:
const {authorize} = require('./authorization')
async function check() {
//import auth data
console.log(authorize(data))
if (authorize(data)) {
//resolve to true
} else {
//reject
}
}
Then in the test case, I called:
afterEach(authorizationStub.restorStub);
describe('auth testing', () => {
it('test successful', () => {
authorizationStub.stubSuccessCall();
return check().then(res => {expect(res.result).to.equl(true)});
})
it('test failed', () => {
authorizationStub.stubFailedCall();
return check().then(res => {expect(res.result).to.equl(false)});
})
})
It's a overly simplified auth logic and test case. The weird problem I had is that if I run both test cases, it prints out:
1 - test successful
true // from console.log(authorize(data))
2 - test failed
true // from console.log(authorize(data))
The 1st test case passed and the 2nd one failed (because the stubbing didn't return the right result)
but in the test failed, case, it supposed to return false as how I stub it in stubFailedCall, but it still has the same result in stubSuccessCall. I have verified the restoreStub is called.
I accidentally came across the fix: In the middleware, I used to have:
const {authorize} = require('./authorization')
...
but if I changed this to:
const auth = require('./authorization')
and then in the codes, instead of:
authorize(data)
I do:
auth.authorize(data)
This will work - both test cases will pass without any other changes.
My question is, why sinon stub/restoring stub didn't work if I deconstruct the authorize call in the middleware, but if I use an object to make the call, it will work? What's the mechanism behind this?

How to test the type of a thrown exception in Jest

I'm working with some code where I need to test the type of an exception thrown by a function (is it TypeError, ReferenceError, etc.?).
My current testing framework is AVA and I can test it as a second argument t.throws method, like here:
it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
t.is(error.message, 'UNKNOWN ERROR');
});
I started rewriting my tests in Jest and couldn't find how to easily do that. Is it even possible?
In Jest you have to pass a function into expect(function).toThrow(<blank or type of error>).
Example:
test("Test description", () => {
const t = () => {
throw new TypeError();
};
expect(t).toThrow(TypeError);
});
Or if you also want to check for error message:
test("Test description", () => {
const t = () => {
throw new TypeError("UNKNOWN ERROR");
};
expect(t).toThrow(TypeError);
expect(t).toThrow("UNKNOWN ERROR");
});
If you need to test an existing function whether it throws with a set of arguments, you have to wrap it inside an anonymous function in expect().
Example:
test("Test description", () => {
expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});
It is a little bit weird, but it works and IMHO is good readable:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
try {
throwError();
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
The Catch block catches your exception, and then you can test on your raised Error. Strange expect(true).toBe(false); is needed to fail your test if the expected Error will be not thrown. Otherwise, this line is never reachable (Error should be raised before them).
#Kenny Body suggested a better solution which improve a code quality if you use expect.assertions():
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
expect.assertions(1);
try {
throwError();
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
See the original answer with more explanations: How to test the type of a thrown exception in Jest
EDIT 2022:
To use this approach and not trigger no-conditional-expect rule (if you're using eslint-plugin-jest), documentation of this rule suggest to use error wrapper:
class NoErrorThrownError extends Error {}
const getError = async <TError>(call: () => unknown): Promise<TError> => {
try {
await call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
describe('when the http request fails', () => {
it('includes the status code in the error', async () => {
const error = await getError(async () => makeRequest(url));
// check that the returned error wasn't that no error was thrown
expect(error).not.toBeInstanceOf(NoErrorThrownError);
expect(error).toHaveProperty('statusCode', 404);
});
});
See: no-conditional-expect docs
I use a slightly more concise version:
expect(() => {
// Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')
From my (albeit limited) exposure to Jest, I have found that expect().toThrow() is suitable if you want to only test an error is thrown of a specific type:
expect(() => functionUnderTest()).toThrow(TypeError);
Or an error is thrown with a specific message:
expect(() => functionUnderTest()).toThrow('Something bad happened!');
If you try to do both, you will get a false positive. For example, if your code throws RangeError('Something bad happened!'), this test will pass:
expect(() => functionUnderTest()).toThrow(new TypeError('Something bad happened!'));
The answer by bodolsog which suggests using a try/catch is close, but rather than expecting true to be false to ensure the expect assertions in the catch are hit, you can instead use expect.assertions(2) at the start of your test where 2 is the number of expected assertions. I feel this more accurately describes the intention of the test.
A full example of testing the type and message of an error:
describe('functionUnderTest', () => {
it('should throw a specific type of error.', () => {
expect.assertions(2);
try {
functionUnderTest();
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
expect(error).toHaveProperty('message', 'Something bad happened!');
}
});
});
If functionUnderTest() does not throw an error, the assertions will be be hit, but the expect.assertions(2) will fail and the test will fail.
I manage to combine some answers and end up with this:
it('should throw', async () => {
await expect(service.methodName('some#email.com', 'unknown')).rejects.toThrow(
HttpException,
);
});
Modern Jest allows you to make more checks on a rejected value. For example, you could test status code of http exception:
const request = Promise.reject({statusCode: 404})
await expect(request).rejects.toMatchObject({ statusCode: 500 });
will fail with error
Error: expect(received).rejects.toMatchObject(expected)
- Expected
+ Received
Object {
- "statusCode": 500,
+ "statusCode": 404,
}
Further to Peter Danis' post, I just wanted to emphasize the part of his solution involving "[passing] a function into expect(function).toThrow(blank or type of error)".
In Jest, when you test for a case where an error should be thrown, within your expect() wrapping of the function under testing, you need to provide one additional arrow function wrapping layer in order for it to work. I.e.
Wrong (but most people's logical approach):
expect(functionUnderTesting();).toThrow(ErrorTypeOrErrorMessage);
Right:
expect(() => { functionUnderTesting(); }).toThrow(ErrorTypeOrErrorMessage);
It's very strange, but it should make the testing run successfully.
In case you are working with Promises:
await expect(Promise.reject(new HttpException('Error message', 402)))
.rejects.toThrowError(HttpException);
You must wrap the code of the function that you are expecting in another arrow function, otherwise the error will not be caught and the assertion will fail.
the function you want to test :
const testThrowingError = () => {
throw new Error();
};
the test:
describe("error function should Throw Error", () => {
expect(() =>testThrowingError()).toThrowError();
});
resource:
https://jestjs.io/docs/expect#tothrowerror
I haven't tried it myself, but I would suggest using Jest's toThrow assertion. So I guess your example would look something like this:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
expect(t).toThrowError('UNKNOWN ERROR');
//or
expect(t).toThrowError(TypeError);
});
Again, I haven't test it, but I think it should work.
Check out toThrow method.
You must wrap the code in an additional function callback!
You should check both: the error message and its type.
For example:
expect(
() => { // additional function wrap
yourCodeToTest();
}
).toThrow(
new RangeError('duplicate prevArray value: A')
);
Because of additional callback wrap, the code will not be run immediately, so jest will be able to catch it.
You should always check the error message to be sure you are checking the correct throw case and not getting another error your code may throw.
It is also nice to check the error type, so the client code may rely on it.
Jest has a method, toThrow(error), to test that a function throws when it is called.
So, in your case you should call it so:
expect(t).toThrowError(TypeError);
The documentation.
I have successfully used this
await expect(
async () => await apiCalls()
).rejects.toThrow();
There's a way to wait an error that comes from a async function, you just have to write your code like in the example bellow
await expect(yourAsyncFunction()).rejects.toThrowError();
The documentation is clear on how to do this. Let's say I have a function that takes two parameters and it will throw an error if one of them is null.
function concatStr(str1, str2) {
const isStr1 = str1 === null
const isStr2 = str2 === null
if(isStr1 || isStr2) {
throw "Parameters can't be null"
}
... // Continue your code
Your test
describe("errors", () => {
it("should error if any is null", () => {
// Notice that the expect has a function that returns the function under test
expect(() => concatStr(null, "test")).toThrow()
})
})
I ended up writing a convenience method for our test-utils library
/**
* Utility method to test for a specific error class and message in Jest
* #param {fn, expectedErrorClass, expectedErrorMessage }
* #example failTest({
fn: () => {
return new MyObject({
param: 'stuff'
})
},
expectedErrorClass: MyError,
expectedErrorMessage: 'stuff not yet implemented'
})
*/
failTest: ({ fn, expectedErrorClass, expectedErrorMessage }) => {
try {
fn()
expect(true).toBeFalsy()
} catch (err) {
let isExpectedErr = err instanceof expectedErrorClass
expect(isExpectedErr).toBeTruthy()
expect(err.message).toBe(expectedErrorMessage)
}
}
A good way is to create custom error classes and mock them. Then you can assert whatever you want.
MessedUpError.ts
type SomeCrazyErrorObject = {
[key: string]: unknown,
}
class MessedUpError extends Error {
private customErrorData: SomeCrazyErrorObject = {};
constructor(err?: string, data?: SomeCrazyErrorObject) {
super(err || 'You messed up');
Object.entries(data ?? {}).forEach(([Key, value]) => {
this.customErrorData[Key] = value;
});
Error.captureStackTrace(this, this.constructor);
}
logMe() {
console.log(this.customErrorData);
}
}
export default MessedUpError;
messedUpError.test.ts
import MessedUpError from './MessedUpError';
jest.mock('./MessedUpError', () => jest.fn().mockImplementation((...args: any[]) => ({
constructor: args,
log: () => {},
})));
type MessedUpErrorContructorParams = Expand<typeof MessedUpError['prototype']>
const MessedUpErrorMock = MessedUpError as unknown as jest.Mock<MessedUpError, [MessedUpErrorContructorParams]>;
const serverErrorContructorCall = (i = 0) => ({
message: MessedUpErrorMock.mock.calls[i][0],
...MessedUpErrorMock.mock.calls[i][1] || {},
});
beforeEach(() => {
MessedUpErrorMock.mockClear();
});
test('Should throw', async () => {
try {
await someFunctionThatShouldThrowMessedUpError();
} catch {} finally {
expect(MessedUpErrorMock).toHaveBeenCalledTimes(1);
const constructorParams = serverErrorContructorCall();
expect(constructorParams).toHaveProperty('message', 'You messed up');
expect(constructorParams).toHaveProperty('customErrorProperty', 'someValue');
}
});
The assertions always go inside the finally clause. This way it will always be asserted. Even if the test does not throw any errors.
There is also an easier way to assert against the error message. The beauty of this method is that you don't need to reconstruct the error object or have the full error message. As long as your error contains part of the error message we can assume it is of the correct type. i.e
const printOnlyString = (str) => {
if(typeof str !== "string"){
throw Error("I can only print strings ${typeof str) given");
}
else {
console.log(str);
}
}
expect(() => printOnlyString(123)).toThrow(/can only print strings/)
Try:
expect(t).rejects.toThrow()

Compare errors in Chai

I needed to implement my own error class in ES6 (with node v4):
class QueryObjectError {
constructor (message) {
this.message = message;
}
}
I have a portion of code that throws said error type:
function myFunct () {
throw new QueryObjectError('a message');
}
And I am using Mocha and Chai to test the the function throws the expected error with the expected message:
it('is a test', function (done) {
var err = new QueryObjectError('abc');
assert.throw(myFunct, err);
done();
});
The test passes although the QueryObjectError objects have different messages and I want to test the case in which deep equality is checked. Any way to solve this with the given tools?
There are two salient issues with your code:
You do not use assert.throw correctly. You should pass the constructor to the expected exception as the 2nd argument and a regular expression or a string as the 3rd argument. If the 3rd argument is a string, Chai will check that the string exist in the exception's message. If it is a regular expression, it will test whether the message is matched by the expression.
Your exception should have a toString method that returns the message, otherwise Chai won't know how to check the message.
Here is an example showing a failure and a success:
import { assert } from "chai";
class QueryObjectError {
constructor (message) {
this.message = message;
}
toString() {
return this.message;
}
}
function myFunct () {
throw new QueryObjectError('a message');
}
it('is a test', function () {
assert.throw(myFunct, QueryObjectError, 'abc');
});
it('is another test', function () {
assert.throw(myFunct, QueryObjectError, /^a message$/);
});

Categories