2

I am trying to mock a Sails Model using sinon.js. I have a problem when I am testing the part where I'm using .fetch() alongside Model.create() to retrieve the newly created row.

Here is my code I want to mock:

...
let newObject = await Object.create(newObjectData).fetch();
...

Here is my test code

const sinon = require('sinon');
const supertest = require('supertest');

describe('Object create action', function() {
  let sandbox;
  beforeEach(function() {
    sandbox = sinon.createSandbox();
  });
  afterEach(function() {
    sandbox.restore();
  });

  it('should create Object', function(done) {

    const objectCreateStub = sandbox.stub(Object, 'create').callsFake(async function(data) {
      console.log(data);
      return {
        key: 'value'
      };
    });
    supertest(sails.hooks.http.app)
      .post(`/objects`)
      .send({
        key: 'value'
      })
      .expect(200)
      .end(done);
  });
});

I have no idea what the Object.create faked function should return in order to .fetch to not throw an error. So, as expected I get this error:

TypeError: Object.create(...).fetch is not a function

What kind of object does Model.create() returns so I could mock it too? Is there a best practice somewhere for testing with Sails and Waterline?

Thanks!

benomite
  • 848
  • 7
  • 23
  • My main question where would be what you want to accomplish with the test. Testing that you create an object with waterline, and that it works, is just part of waterline unit tests, so you don't really need to test that. If you want to mock the result of a call to a controller, I would use the `nock` module https://www.npmjs.com/package/nock – Luis Lobo Borobia Mar 14 '19 at 15:37
  • I was just trying to test my api actions without deploying a test database. – benomite Mar 19 '19 at 16:05
  • How do you feel about using fixtures? If you think I can add an answer about one way of using fixtures for your tests – Luis Lobo Borobia Mar 21 '19 at 22:03

1 Answers1

2

I figured this out, but I am still unable to say how this works and how it should be tested (correctely)

The problem with my code was: only the last function called in the chain is "async" so when mocking this, only fetch must be async

Here is my test code

const objectCreateStub = sandbox.stub(Object, 'create').callsFake(function (data) { // Notice here fake function IS NOT async
  console.log(data
  return {
    fetch: async () => {  // Here we "mock" the fetch function and it MUST be async !
      return newTest
    }
  };
});

And if you are not using fetch, mock should look like this:

const objectCreateStub = sandbox.stub(Object, 'create').callsFake(async function(data) {  // here fake function MUST BE async
  console.log(data)
});

If you understand how Waterline queries works and why we should do this workaround please post another answer because my code works but I still have so many questions :/

benomite
  • 848
  • 7
  • 23
  • You should be able to use the first piece of test code all the time, right? Because your source code should not expect `Model.create()` (without fetch) to return anything. So, if the stub does return something (an object with the `fetch` property) your source code should ignore it. – carpiediem Mar 15 '19 at 03:05
  • 1
    You are right, but keep in mind that with this solution you always have to wonder which function is called last in the query chain. Here is an example with `.fetch()` but this is the same with `.limit()` or `.sort()` etc... __only last function should return an async fn__ So first piece won't work every time. – benomite Mar 19 '19 at 16:03
  • 1
    Good point. I tend to avoid `.limit()` and `.sort()` in my code (preferring one big criteria object), but that's not ideal for everyone. – carpiediem Mar 20 '19 at 15:11