Question: Stubbing Models contained within an Object

I am having real issues stubbing one particular thing using sinon.  I have a simple function I am testing
const floatAPIModels = require("models/float/floatAPIModels");

const insertFloatData = async (databaseConnection, endpoint, endpointData) => {
  try {
    const floatModel = floatAPIModels(databaseConnection);
    await databaseConnection.sync();

    if (endpoint === "people") {
      endpointData.forEach(async (record) => {
        await floatModel.Person.upsert(record);
      });
    }

    return true;
  } catch (error) {
    console.log("Unable to insert data into the database:", error);
    return error;
  }
};

The problem is with floatAPIModels being an Object that returns things.  My implementation is this
const { DataTypes } = require("sequelize");

const floatAPIModels = (sequelize) => {
  const Person = sequelize.define(
    "Person",
    {
      people_id: { type: DataTypes.INTEGER, primaryKey: true },
      job_title: { type: DataTypes.STRING(200), allowNull: true },
      employee_type: { type: DataTypes.BOOLEAN, allowNull: true },
      active: { type: DataTypes.BOOLEAN, allowNull: true },
      start_date: { type: DataTypes.DATE, allowNull: true },
      end_date: { type: DataTypes.DATE, allowNull: true },
      department_name: { type: DataTypes.STRING, allowNull: true },
      default_hourly_rate: { type: DataTypes.FLOAT, allowNull: true },
      created: { type: DataTypes.DATE, allowNull: true },
      modified: { type: DataTypes.DATE, allowNull: true },
    },
    {
      timestamps: true,
      tableName: "Person",
    }
  );

  return {
    Person,
  };
};

module.exports = floatAPIModels;

I have removed some things to cut down on code.  At the moment I am doing something like this
const { expect } = require("chai");
const sinon = require("sinon");
const floatAPIModels = require("src/models/float/floatAPIModels");
const floatService = require("src/services/float/floatService");

describe("insertFloatData", () => {
  let databaseConnection;
  let floatModelMock;

  beforeEach(() => {
    databaseConnection = {};

    floatModelMock = {
      Person: { upsert: sinon.stub().resolves() },
    };

    sinon.stub(floatAPIModels, "Person").returns(floatModelMock.Person);
  });

  afterEach(() => {
    sinon.restore();
  });

  it("should insert endpointData into the 'people' endpoint", async () => {
    const endpoint = "people";
    const endpointData = [{ record: "data" }];

    await floatService.insertFloatData(databaseConnection, endpoint, endpointData);

    expect(floatModelMock.Person.upsert.calledOnce).to.be.true;
    expect(floatModelMock.Person.upsert.firstCall.args[0]).to.deep.equal(endpointData[0]);
  });
});

With the above, I get
TypeError: Cannot stub non-existent property Person

But I have tried default, and a lot of other ways, but none of them seems to work.
How can I properly stub this and get the unit test working?
Thanks


Answer:
floatAPIModels is a function that returns { Person } object. There is no Person property on this function. That's why you got the error.
In order to stub the floatAPIModels function, I will use the proxyquire module to do this.
E.g.
model.js:
const { DataTypes } = require("sequelize");

const floatAPIModels = (sequelize) => {
  const Person = sequelize.define(
    "Person",
    {
      people_id: { type: DataTypes.INTEGER, primaryKey: true },
      // rest fields, don't matter for this test
      // ...
    },
    { timestamps: true, tableName: "Person", }
  );

  return {
    Person,
  };
};

module.exports = floatAPIModels;

service.js:
const floatAPIModels = require("./model");

const insertFloatData = async (databaseConnection, endpoint, endpointData) => {
  try {
    const floatModel = floatAPIModels(databaseConnection);
    await databaseConnection.sync();
    if (endpoint === "people") {
      endpointData.forEach(async (record) => {
        await floatModel.Person.upsert(record);
      });
    }
    return true;
  } catch (error) {
    console.log("Unable to insert data into the database:", error);
    return error;
  }
};

module.exports = { insertFloatData }

service.test.js:
const sinon = require("sinon");
const proxyquire = require('proxyquire');

describe("insertFloatData", () => {
  let databaseConnection;

  beforeEach(() => {
    databaseConnection = {
      define: sinon.stub(),
      sync: sinon.stub()
    };
  });

  afterEach(() => {
    sinon.restore();
  });

  it("should insert endpointData into the 'people' endpoint", async () => {
    const endpoint = "people";
    const endpointData = [{ record: "data" }];
    const PersonStub = {
      upsert: sinon.stub()
    }
    const floatAPIModelsStub = sinon.stub().returns({ Person: PersonStub })
    const floatService = proxyquire('./service', {
      './model': floatAPIModelsStub
    })

    await floatService.insertFloatData(databaseConnection, endpoint, endpointData);

    sinon.assert.calledOnce(PersonStub.upsert)
    sinon.assert.match(PersonStub.upsert.firstCall.args[0], endpointData[0])
  });
});

Test result:
  insertFloatData
    ✓ should insert endpointData into the 'people' endpoint (4168ms)


  1 passing (4s)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |   76.47 |       50 |   66.67 |   76.47 |                   
 model.js   |      60 |      100 |       0 |      60 | 4-24              
 service.js |   83.33 |       50 |     100 |   83.33 | 14-15             
------------|---------|----------|---------|---------|-------------------