Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model instance creation assistance #149

Closed
Czechh opened this issue Oct 23, 2016 · 4 comments
Closed

Model instance creation assistance #149

Czechh opened this issue Oct 23, 2016 · 4 comments

Comments

@Czechh
Copy link

Czechh commented Oct 23, 2016

Hello, I'm giving a try to td and so far I really enjoy it! But I've encountered a halt when I'm trying to create an instance of an mongoose object. I want to test the userCreate and for that I've tried several approaches and all have failed.

The function to be tested:

UserController.prototype.createUser = function createUser (req, res, next) {
  //`User` is a mongoose Model (just an object with a contructor and some instance methods)
  let user = new User({
    name: req.body.name,
    username: req.body.username,
    email: req.body.email
  });

  return Promise.resolve(user.save())
    .then((newUser) => {
      return res.send(newUser);
    })
    .catch(err => {
      next(Boom.wrap(err));
    })
};

So my initial approach is:

it('creates user', (done) => {
      let userBody = {
        name: 'test-name',
        username: 'test-username',
        email: 'test-email'
      };

      let mockRequest = { body: userBody };
      const userInstance = new UserModel({ userBody })

      var user = td.object(userInstance);
      td.replace(UserModel.prototype, 'save');

      td.when(user.save())
        .thenReturn(Promise.resolve(userBody))

      var mockResponse = {
          send: td.function()
      };

      td.when(mockResponse.send(userBody))
          .thenDo(() => done());

      controller.createUser(mockRequest, mockResponse);
    });

But done() is never called, so my next try involves trying to replace the class with no success. I found an Issue regarding constructors, but haven't been able to work this out. Is there a better way to approach objects as such?

@Czechh
Copy link
Author

Czechh commented Oct 23, 2016

Another try:

it('creates user', (done) => {
      let userBody = {
        name: 'test-name',
        username: 'test-username',
        email: 'test-email'
      };

      let mockRequest = { body: userBody };

      const user = new UserModel(userBody);

      const constructor = td.object(UserModel);;

      td.when(constructor({userBody}))
        .thenReturn(user)

      td.replace(user, 'save');

      td.when(Promise.resolve(user.save()))
        .thenReturn(userBody)

      var mockResponse = {
          send: td.function()
      };

      td.when(mockResponse.send(userBody))
          .thenDo(() => done());

      controller.createUser(mockRequest, mockResponse);
    });

But with no success, I'm getting now TypeError: Cannot read property 'scope' of undefined, that tracks with a stack:

      at model.get (node_modules/mongoose/lib/document.js:1757:38)
      at node_modules/testdouble/lib/object.js:58:47
      at arrayReduce (node_modules/lodash/_arrayReduce.js:21:19)
      at Object.reduce (node_modules/lodash/reduce.js:48:10)
      at createTestDoublesForPrototype (node_modules/testdouble/lib/object.js:57:14)
      at createTestDoubleObject (node_modules/testdouble/lib/object.js:34:14)
      at Object.module.exports [as object] (node_modules/testdouble/lib/object.js:25:18)
      at Context.<anonymous> (test/unit/controllers/user.test.js:83:30)

So I'm not mocking the object properly. I'm going to keep trying and post my development if I can make it work.

I hope it's ok for me to post here since I'm new with testing.

@Czechh
Copy link
Author

Czechh commented Oct 25, 2016

My approach was wrong, and I switched to dependency injection and some tests are now going through, but the problem still exists with the contructor. My function is now a little different:

UserController.prototype.createUser = function createUser (req, res, next) {
  const { username, name, email } = req.body;

  return Promise.resolve(this.User({ username, name, email }))
    .then(user => user.save())
    .then(newUser => res.send(newUser))
    .catch(err => {
      console.log(err);
      next(Boom.wrap(err))
    })
};

And I'm able to mock different REST controller function except this one. I'm trying:

it('without any errors', (done) => {
      let userBody = {
        name: 'test-name',
        username: 'test-username',
        email: 'test-email'
      };

      let mockRequest = { body: userBody };

      var mockResponse = {
          send: td.function()
      };

      controller.User = td.function()

      td.when(controller.User(userBody))
        .return(Promise.resolve(userBody));

      td.when(mockResponse.send(userBody))
          .thenDo(() => done());

      controller.createUser(mockRequest, mockResponse);
    });

With no success as the console shows:

1) UserController createUser without any errors:
     TypeError: _testdouble2.default.when(...).return is not a function
      at Context.<anonymous> (__test__/unit/controllers/user.test.js:91:7)

@Czechh
Copy link
Author

Czechh commented Oct 25, 2016

My beforeEach block include a way for other functionality to work, and they all pass

  controller = new UserController();
  controller.User = {
      findById: td.function(),
      findByIdAndUpdate: td.function(),
      save: td.function()
  };

But since I'm not sure how to mock the constructor function, I'm still reading about this.

@Czechh
Copy link
Author

Czechh commented Oct 25, 2016

I solve this problem creating a mockModel and instantiating the controller using that mock like so:

mockModel = td.function()
mockModel.findById = td.function();
mockModel.findByIdAndUpdate = td.function();
mockModel.findByIdAndRemove = td.function();
mockModel.save = td.function();

controller = new UserController({ User: mockModel });

This way at least I have the certainty that the controller is calling those model functions correctly.

The final piece is to also create a mockUser when a user is created. And add a tesdouble function to it. => userMock = { save: td.function() }

Now the test can occur:

    it('without any errors', (done) => {
      let userBody = {
        name: 'test-name',
        username: 'test-username',
        email: 'test-email'
      };

      let mockRequest = { body: userBody };

      var mockResponse = {
          send: td.function()
      };

      // New User created from mockModel
      let mockUser = {
        save: td.function()
      }

      td.when(controller.User(userBody))
        .thenReturn(Promise.resolve(mockUser));

      td.when(mockUser.save())
        .thenReturn(Promise.resolve(userBody))

      td.when(mockResponse.send(userBody))
          .thenDo(() => done());

      controller.createUser(mockRequest, mockResponse);
    });

I'm learning about this as I go, I hope it is not spamming the team developing this tool.

@Czechh Czechh closed this as completed Oct 25, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant