not sure what I am doing wrong but it seems fairly simple in my head. I have a class that creates a new Session class inside it. If the MainClass 's foo function throws, session's deleteEmpty function is called.
I am trying in my test to ensure that thrown has a value and that the deleteEmpty is called. When I have the thrown assertion itself, it passes. When I add the assertion to check deleteEmpty is called, it fails but the console has no output as to why.
MainClass
const token = "12345";
const data = { /// };
const session = new Session(token, data);
async function foo() {
try {
//
} catch(error) {
await this.session.deleteEmpty();
throw error;
}
}
testFile
const sessionMock = Sinon.createStubInstance(Session)
let thrown: Error | undefined = undefined;
sandbox.stub(MainClass, 'foo').throws();
try {
await MainClass.foo();
} catch(error) {
thrown = error;
}
expect(sessionMock.deleteEmpty).calledOnce;
expect(thrown).to.not.be.undefined;
Related
Hi I am using sequelize ORM with Postgres database for this node.js express.js app. As for testing I am using mocha, chai and sinon.
I am trying to complete a test for a class's method. The class instant i call it userService and the method is findOneUser .. This method has got an argument id .. So in this moment I want to test for a throw error the test works if I dont put an argument. That means this test is obviously not complete.
Here is the class method I want to test
userService.js
module.exports = class UserService {
async findOneUser(id) {
try {
const user = await User.findOne({ where: { id: id } }); // if null is returned error is thrown
if (!user) {
throw createError(404, "User not found");
}
return user;
} catch (err) {
throw err
}
}
}
And here is my test code
userService.spec.js
describe.only("findOne() throws error", () => {
let userResult;
const error = customError(404, "User not found"); // customError is a function where I am throwing both status code and message
before("before hook last it block issue withArgs" , async () => {
// mockModels I have previously mocked all the models
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).resolves(null); // basically have this called and invoked from calling the method that it is inside of based on the argument fakeId
userResult = sinon.stub(userService, "findOneUser").throws(error); // 🤔this is the class instances method I wonder how to test it withArguments anything I try not working but SEE BELOW COMMENTS🤔
});
after(() => {
sinon.reset()
});
it("userService.findOneUser throws error works but without setting arguments 🤔", () => {
expect(userResult).to.throw(error);
});
/// this one below still not working
it("call User.findOne() with incorrect parameter,, STILL PROBLEM 🤯", () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
});
But for the method of my class findOneUser has an argument (id) how can I pass that argument into it where I am stubbing it?
Or even any ideas on how to fake call the class method?? I want both it blocks to work
EDIT
I forgot to mention I have stubbed the mockModels.User already and that was done before the describe block
const UserModel = {
findByPk: sinon.stub(),
findOne: sinon.stub(),
findAll: sinon.stub(),
create: sinon.stub(),
destroy: sinon.stub()
}
const mockModels = makeMockModels( { UserModel } );
// delete I am only renaming UserModel to User to type things quicker and easier
delete Object.assign(mockModels, {['User']: mockModels['UserModel'] })['UserModel']
const UserService = proxyquire(servicePath, {
"../models": mockModels
});
const userService = new UserService();
const fakeUser = { update: sinon.stub() }
SOLUTION
I think this might be the solution to my problem strange but it works the tests is working with this
describe.only("findOne() throws error", async () => {
const errors = customError(404, "User not found"); // correct throw
const errors1 = customError(404, "User not foundss"); // on purpose to see if the test fails if should.throw(errors1) is placed instead of should.throw(errors)
after(() => {
sinon.reset()
});
// made it work
it("call User.findOne() with incorrect parameter, and throws an error, works some how! 🤯", async () => {
userResult = sinon.spy(userService.findOneUser);
try {
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
await userResult(fakeId);
} catch(e) {
// pass without having catch the test fails 😵💫
} finally {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
}
});
it("throws error user does not exist,,, WORKS", () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw
mockModels.User.findOne.withArgs(fakeId).should.throw(errors); // specially this part without having the catch test fails. but now test works even tested with errors1 variable
expect(userResult).to.throw;
});
});
MORE CLEANER SOLUTION BELOW
I like this solution more as I call the method inside within the describe block, then I do the test of the two it blocks.
The problem was I should not have stubbed the class method but should have called it directly, or used sinon.spy on the method and call it through the spy. As for checking that the errors are as expected then running this line of expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors); was the solution I needed.
describe.only('findOne() user does not exists, most cleanest throw solution', () => {
after(() => {
sinon.reset()
});
const errors = customError(404, "User not found");
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
userResult = sinon.spy(userService, "findOneUser"); // either invoke the through sinon.spy or invoke the method directly doesnt really matter
userResult(fakeId);
// userResult = userService.findOneUser(fakeId); // invoke the method directly or invoke through sinon.spy from above
it('call User.findOne() with invalid parameter is called', () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
it('test to throw the error', () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors);
expect(userResult).to.throw;
})
});
I have a few classes which use 'dns' from node.js. But when an error occurs, my app is thrown. I made a siimple example with classes and throwable functions and I faced with the same problem. It's works if an exception is thrown from function but it doesn't work if an exception is thorwn from class.
Example:
class Test {
constructor() {
this.t();
}
async t() {
throw new Error("From class");
}
}
async function test(){
new Test();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Output:
Uncaught (in promise) Error: From class
at Test.t (<anonymous>:6:11)
at new Test (<anonymous>:3:10)
at test (<anonymous>:11:3)
at <anonymous>:15:3
How to catch errors from try/catch block in this example?
UPD:
Full code (typescript):
export class RedisService {
client: any;
expirationTime: any;
constructor(args: RedisServiceOptions) {
let redisData: any = {};
if (args.password)
redisData["defaults"] = { password: args.password };
dns.resolveSrv(args.host, (err, addresses) => {
if (err) {
/// Handling error in main func
}
else {
log.info("Using Redis cluster mode");
redisData["rootNodes"] = addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return Object({ url: `redis://${address.name}:${address.port}` })
});
this.client = createCluster(redisData);
};
this.client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
this.client.connect().then(() => { log.info("Connected to Redis") });
});
this.expirationTime = args.expirationTime;
}
/// Class functions
}
it doesn't work if an exception is thrown from class.
In particular, when an asynchronous error event occurs in the constructor, yes. Like your question title says, you can't handle errors outside of an async context, and a constructor is not that.
Your current implementation has many issues, from client being undefined until it is initialised to not being able to notify your caller about errors.
All this can be solved by not putting asynchronous initialisation code inside a constructor. Create the instance only once you have all the parts, use an async helper factory function to get (and wait for) the parts.
export class RedisService {
client: RedisClient;
expirationTime: number | null;
constructor(client: RedisClient, expirationTime: number | null) {
this.client = client;
this.expirationTime = expirationTime;
}
static create(args: RedisServiceOptions) {
const addresses = await dns.promises.resolveSrv(args.host);
log.info("Using Redis cluster mode");
const redisData = {
defaults: args.password ? { password: args.password } : undefined,
rootNodes: addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return { url: `redis://${address.name}:${address.port}` };
}),
};
const client = createCluster(redisData);
client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
await this.client.connect();
log.info("Connected to Redis");
return new RedisService(client, args.expirationTime);
}
… // instance methods
}
Now in your main function, you can call create, use await, and handle errors from it:
async function main(){
try {
const service = await RedisService.create(…);
} catch(e) {
console.log("From async catch", e);
}
}
You generate multiple async-requests but you can only catch errors from the first one:
You create a promise with async function test().
Then you create a syncronous call within it, with new Test(), every error syncronously thrown from within it will be catched by the catch.
Then you generate another promise call from within the syncronous constructor, this error can't be caught by the try/catch-block or .catch at, or above the async function test().
It is similar to this:
constructor() {
new Promise(() => throw new Error(''))
}
So you have 3 possible ways to solve it:
You catch the error inside the async t() {}-call.
You catch it inside the constructor with this.t().catch(console.error) (which can't be forwarded to the try/catch block) as it is forwarded to the catch block of the Promise behind the async call. And if there is no .catch on the async call, you get the "Unhandled Promise rejection"-Error.
Don't call the async function from within the constructor at all, use it like this:
class Test {
async t() {
throw new Error("From class");
}
}
async function test(){
await (new Test()).t();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Don't make async methods)))
Your solution looks somewhat like this.
class Test {
constructor() {
this.t();
}
t() {
(async () => {
try {
throw new Error("From class");
} catch (e) {
console.log(e);
}
})();
}
}
Have a nice rest of your day
Is there a way to mock what a thrown exception in the try catch block? I want to test my function that the url string in error.message is indeed replaced with replacedURL string. How can I easily test it or mock the thrown exception to an object with this URL.
async function getUsers() {
try {
const response = await fetch('/api/users');
if (response.status >= 400) {
console.error('Could not fetch users');
return;
}
const users = response.json();
return users;
} catch (error) {
let message = error.message;
if (message) {
message = message.replace(/https:\/\/some.url.com /g, 'replacedURL');
delete error.message;
}
console.error('error', error);
}
}
Here's my test
test('url in error message is replaced', () => {
expect(getUsers()).toThrow('replacedURL');
})
You might want to actually throw an error in your function for it to be caught by toThrow.
Also, I'd recommend that your test doesn't actually tries fetching from the API (unless you're sure that's what you want it to do). Instead, consider using a custom caller (which can extend fetch) which you can then mock the response for, like const caller = jest.fn().mockRejectedValue({ message: 'https:://some.url.com' });
Let's say I have a function like this:
const getPlayer = (id) => {
return players[id;]
}
//--------------------------
const client = getPlayer(9);
How can I return the err parameter to the client variable if no player is found? For example:
if (client.err) {
//do something
}
I tried passing the error via throw new Error('my error') , but the function still doesn't get it, what am I doing wrong?:(
So your first instinct was correct, you should use the 'throw' keyword to raise an error. To act on the error you need to use try/catch like I've done below.
const getPlayer = (id) => {
if(id in players) {
return players[id];
}
throw new Error("Oh noes...!");
}
try {
const client = getPlayer(9);
} catch(error) {
console.log(error.message);
}
When an error is thrown inside a function being executed in a try block, execution immediately jumps to the catch block, allowing you to respond to the error appropriately.
Checkout try/catch syntax for that.
For example:
const getPlayer = (id) => {
if (!id) {
throw new Error('no id provided');
}
return players[id]
}
To get this "error" state, when it triggers you can do following:
try {
const client = getPlayer(null);
} catch(error) {
console.log(error.message);
}
I have tried something but not sure if this is what you are after:
let a = (x) => {
if (x == 0) {
throw new Error("Votes are zero");
} else {
return x;
}
};
Run it in the console with the values as a(0) --> will throw you a new error and a(5)
I want to make a typed async function with proper error handling.
I can define one like this:
export async function doSomething(userId:string) : Promise<ISomething | void> {
let somthing: ISomething = {};
try {
something.user = await UserDocument.findById(userId);
something.pet = await PetDocument.findOne({ownerId:userId});
return Promise.resolve(something);
} catch (err){
console.log("I would do some stuff here but I also want to have the caller get the error.");
return Promise.reject(err);
}
}
...which seems to work, but (for reasons that are clear), if I try to assign the result to an ISomething object, I get the error Type 'void | ISomething' is not assignable to type 'ISomething'.
let iSomething:ISomething;
iSomething = await doSomething('12'); //this give me the error
I get why that is. My question is, what pattern should I use for error handling in a case like this? Note that if the return type is Promise<IProfile> instead then I get an error for the return Promise.reject(err); line (which would return Profile<void>).
In place of the line return Promise.reject(err); I can use throw err;, but there may be times where I'd want to use the Promise.reject pattern (like if I want to do some more things before I return).
I have a feeling that I'm missing something with promises / async, but I can't find typed examples that follow this pattern.
...note that if I use the full Promise pattern it works fine:
doSomething('12')
.then( (something) => {//do stuff})
.catch( (err) => {//handle error});
Should I just be using throw and forget about Promise.reject? If I use throw, will the .catch() be triggered appropriately?
Not returning a promise in the first place is how I usually implement the async await pattern:
export async function doSomething(userId:string) : Promise<ISomething>{
let something: ISomething = {};
try {
something.user = await UserDocument.findById(userId);
something.pet = await PetDocument.findOne({ownerId:userId});
return something;
} catch (err){
console.log("I would do some stuff here but I also want to have the caller get the error.");
throw(err);
}
}
so if you don't have to do intermediate cleanup you could strip it down to:
export async function doSomething(userId:string) : Promise<ISomething>{
let something: ISomething = {};
something.user = await UserDocument.findById(userId);
something.pet = await PetDocument.findOne({ownerId:userId});
return something;
}
and have
the subsequent awaited function catch the exception or
the calling function handle the rejected promise
All fair if you don't want to use Promise.reject and Promise.resolve, but if you do - here's the easy fix.
Get rid of the | void in the signature, and change the rejection returned to return Promise.reject<ISomething>(err);. Works!
Here's the modified version from the question:
export async function doSomething(userId:string) : Promise<ISomething> {
let something: ISomething = {};
try {
something.user = await UserDocument.findById(userId);
something.pet = await PetDocument.findOne({ownerId:userId});
return Promise.resolve(something);
} catch (err){
console.log("I would do some stuff here but I also want to have the caller get the error.");
return Promise.reject<ISomething>(err);
}
}