How do I test a class's method that has arguments using sinon.js

Viewed 41

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() }
1 Answers

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;
      })
    });
Related