Sinon sandboxes not stubbing functions in a controller in a express js app

I tried stubbing functions that were in a controller in an express app and when I called the controller function, assertions I wrote throws an error that the stubbed functions are not called or not called with the required arguments. Below is the test file. When I console.log(createUserStub) I get [Function: createUser]. Also, when I tried console.logging the newUser generated in the controller function, the result printed out shows that the actual internal function, createUser was called and not the stubbed function.

const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const chai = require("chai");

chai.use(sinonChai);
const expect = chai.expect;

const authController = require("./../../src/controllers/auth");
const tokenService = require("./../../src/services/token.js");
const authService = require("./../../src/services/auth.js");

const { generatePassword } = require("./../../src/utils/auth.js");
const sendEmailModule = require("./../../src/utils/sendEmail.js");

describe("Signup Controller", () => {
  let res, req, createUserStub, generateTokenStub, sendEmailStub;
  let sandbox;

  const fakeHashedPassword = "fhfhgjkggklgj";
  const fakeId = "hjklkjhghjkjhghjkjh";
  const fakePictureUrl = "www.picture.com";
  const fakeToken = "rhjkljhghjkjhghj";

  const userInput = {
    name: "Emmanuel Ibekwe",
    email: "[email protected]",
    password: generatePassword(),
    picture: fakePictureUrl
  };

  const newUser = {
    _id: fakeId,
    name: "Emmanuel Ibekwe",
    email: "[email protected]",
    picture: fakePictureUrl
  };

  const fakeCreatedUser = {
    ...userInput,
    password: fakeHashedPassword,
    _id: fakeId
  };

  beforeEach(() => {
    sandbox = sinon.createSandbox();
    console.log("inside beforeEach");
    createUserStub = sandbox.stub(authService, "createUser");

    createUserStub.resolves(fakeCreatedUser);

    generateTokenStub = sandbox.stub(tokenService, "generateToken");
    generateTokenStub.resolves(fakeToken);

    sendEmailStub = sandbox.stub(sendEmailModule, "sendEmail").resolves();
  });

  afterEach(() => {
    console.log("inside afterEach");
    sandbox.restore();
  });

  it("should sign up user successfully", async done => {
    const statusJsonSpy = sandbox.spy();

    res = {
      json: sandbox.spy(),
      status: sandbox.stub().returns({ json: statusJsonSpy })
    };
    req = { body: userInput };

    const next = err => {
      console.log("err", err);
    };

    try {
      await authController.signup(req, res, next);

      // createUserStub();
      expect(createUserStub).to.have.been.calledOnce;
      expect(createUserStub).to.have.been.calledOnceWith(userInput);
      expect(generateTokenStub).to.have.been.calledTwice;
      expect(sendEmailStub).to.have.been.calledOnce;
      expect(res.status).to.have.been.calledOnceWith(201);
      expect(res.json).to.have.been.calledWith({
        message: "sign up successful",
        accessToken: fakeToken,
        refreshToken: fakeToken,
        user: newUser
      });

      done();
    } catch (error) {
      done(error);
    }
  }); //
});

This is the controller

const { createUser, signInUser } = require("./../services/auth.js");
const { generateToken, verifyToken } = require("./../services/token.js");
const createHttpError = require("http-errors");
const User = require("./../models/user.js");
const { sendEmail } = require("./../utils/sendEmail.js");
const PasswordResetCode = require("./../models/PasswordResetCode.js");
const bcryptjs = require("bcryptjs");
const { isPasswordFalse } = require("../utils/validation.js");
const { getRandomSixDigit, generatePassword } = require("./../utils/auth.js");
const dotenv = require("dotenv");
const { OAuth2Client } = require("google-auth-library");

dotenv.config();

const {
  ADMIN_EMAIL,
  FRONT_END_TESTING_DOMAIN,
  FRONT_END_PRODUCTION_DOMAIN
} = process.env;

const signup = async (req, res, next) => {
  try {
    const { name, email, password, picture } = req.body;
    console.log("req.body", { name, email, password, picture });
    const newUser = await createUser({
      name,
      email,
      password,
      picture
    });
    console.log("newUser", newUser);

    const accessToken = await generateToken(
      {
        userId: newUser._id.toString(),
        email: newUser.email
      },
      "1d",
      process.env.ACCESS_TOKEN_SECRET
    );
    console.log("accessToken", accessToken);

    const refreshToken = await generateToken(
      {
        userId: newUser._id.toString(),
        email: newUser.email
      },
      "30d",
      process.env.REFRESH_TOKEN_SECRET
    );

    await sendEmail({
      code: null,
      to: newUser.email,
      subject: "Trackr Sign up",
      name: newUser.name.split(" ")[0],
      type: "sign up"
    });

    console.log(201);

    res.status(201).json({
      message: "sign up successful",
      accessToken,
      refreshToken,
      user: {
        _id: newUser._id,
        name: newUser.name,
        email: newUser.email,
        picture: newUser.picture
      }
    });
  } catch (error) {
    if (!error.status) {
      error.status = 500;
    }
    next(error);
  }
};

Here are the codes of the internal functions.

const createHttpError = require("http-errors");
const validator = require("validator");
const bcryptjs = require("bcryptjs");
const User = require("../models/user.js");
const validation = require("./../utils/validation.js");
const { isPasswordFalse } = validation;

const createUser = async userData => {
  const { name, email, password, picture } = userData;
  // console.log("userData", userData);
  if (!name || !email || !password) {
    throw createHttpError.BadRequest("Please fill all fields");
  }

  // console.log("name", name);
  if (!validator.isEmail(email)) {
    throw createHttpError.BadRequest("email is invalid");
  }

  const checkEmail = await User.findOne({ email: email });

  if (checkEmail) {
    throw createHttpError.Conflict("email already exists. Try a new email.");
  }

  if (isPasswordFalse(password)) {
    throw createHttpError.BadRequest(
      "password must be atleast 8 characters and contain atleast an uppercase, a lowercase, a number or a special character"
    );
  }

  const hashedPassword = await bcryptjs.hash(password, 12);
  // console.log("hashedPassword", hashedPassword);
  const user = await new User({
    name,
    email,
    password: hashedPassword,
    picture: picture || DEFAULT_PICTURE_URL
  });

  return user.save();
};


const tokenUtils = require("./../utils/token.js");
const { sign, verify } = tokenUtils;

const generateToken = async (payload, expiresIn, secret) => {
  const token = await sign(payload, expiresIn, secret);
  return token;
};

I tried testing just the createUser function and all tests were passed. Here is the test file.

const mongoose = require("mongoose");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const chai = require("chai");

chai.use(sinonChai);
const expect = chai.expect;

const authService = require("../../src/services/auth.js");
const bcryptjs = require("bcryptjs");
const User = require("../../src/models/user");
const { generatePassword } = require("../../src/utils/auth");

describe("createUser Service", () => {
  let hashStub, findOneStub, saveStub, sandbox;
  const fakeHashedPassword = "fhfhgjkggklgj";
  const fakeId = "hjklkjhghjkjhghjkjh";
  const fakePictureUrl = "www.picture.com";

  const userInput = {
    name: "Emmanuel Ibekwe",
    email: "[email protected]",
    password: generatePassword(),
    picture: fakePictureUrl
  };

  const fakeCreatedUser = {
    ...userInput,
    password: fakeHashedPassword,
    _id: fakeId
  };

  beforeEach(() => {
    sandbox = sinon.createSandbox();
    hashStub = sandbox.stub(bcryptjs, "hash");
    findOneStub = sandbox.stub(mongoose.Model, "findOne");

    saveStub = sandbox.stub(User.prototype, "save");

    hashStub.resolves(fakeHashedPassword);
    findOneStub.resolves(null);

    saveStub.resolves(fakeCreatedUser);
  });
  afterEach(() => {
    sandbox.restore();
  });

  it("should return a newly created user", async () => {
    newUser = await authService.createUser(userInput);

    expect(newUser).to.deep.equal(fakeCreatedUser);
  });

  it("should throw a BadRequest error if name is not provided", async () => {
    try {
      await authService.createUser({ ...userInput, name: undefined });
    } catch (err) {
      //   console.log(err);
      expect(err.status).to.equal(400);
      expect(err.message).to.equal("Please fill all fields");
    }
  });

  it("should throw a BadRequest error if email is invalid", async () => {
    try {
      await authService.createUser({ ...userInput, email: "undefined" });
    } catch (err) {
      //   console.log(err);
      expect(err.status).to.equal(400);
      expect(err.message).to.equal("email is invalid");
    }
  });

  it("should throw a BadRequest error if email is not provided", async () => {
    try {
      await authService.createUser({ ...userInput, email: undefined });
    } catch (err) {
      //   console.log(err);
      expect(err.status).to.equal(400);
      expect(err.message).to.equal("Please fill all fields");
    }
  });

  it("should throw a Conflict error if user already exists", async () => {
    try {
      await authService.createUser({ ...userInput, password: "hhfjdkdlflf" });
    } catch (err) {
      //   console.log(err);
      expect(err.status).to.equal(400);
      expect(err.message).to.equal(
        "password must be atleast 8 characters and contain atleast an uppercase, a lowercase, a number or a special character"
      );
    }
  });

  it("should throw a BadRequest error if password does not meet the required conditions", async () => {
    findOneStub.resolves(fakeCreatedUser);
    try {
      await authService.createUser(userInput);
    } catch (err) {
      expect(err.status).to.equal(409);
      expect(err.message).to.equal("email already exists. Try a new email.");
    }
  });
});