Search code examples
node.jsunit-testingmockingsinontypeorm

Spy function with params by Sinon.js


I'm trying to write some unit tests of code that uses typeorm without hitting the DB. And I'm using sinon for spy/stub/mock. This is my function.

  async updateDoingToFailedWithLock(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.manager
      .getRepository(Report)
      .createQueryBuilder("report")
      .useTransaction(true)
      .setLock("pessimistic_write")
      .update(Report)
      .set({ status: ReportStatus.FAILED })
      .where(`(status = "doing")`)
      .execute();
  }

I already wrote a fake test to make sure execute() is called by using spy function. But I want to test the params of these functions createQueryBuilder..., the sure the params are correct. I took a look at sinon document and it seems like sinon support test params by this API: spy().withArgs(arg1, arg2...).

But I'm not sure how to spy my function correctly.

describe("updateDoingToFailedWithLock()", (): void => {
    let sandbox: Sinon.SinonSandbox;

    beforeEach(() => (sandbox = Sinon.createSandbox()));
    afterEach(() => sandbox.restore);

    it("should be success", async (): Promise<void> => {
      const fakeManager = {
        getRepository: () => {
          return fakeManager;
        },
        createQueryBuilder: () => {
          return fakeManager;
        },
        useTransaction: () => {
          return fakeManager;
        },
        setLock: () => {
          return fakeManager;
        },
        update: () => {
          return fakeManager;
        },
        set: () => {
          return fakeManager;
        },
        where: () => {
          return fakeManager;
        },
        execute: () => {},
      };
      const fakeQueryRunner = {
        manager: fakeManager,
      };
      const connection = new typeorm.Connection({ type: "mysql" });
      const reportService = new ReportService();
      sandbox.stub(connection, "createQueryRunner").callsFake((): any => {
        return fakeQueryRunner;
      });

      const queryRunner = connection.createQueryRunner();
      const spy = sandbox.spy(fakeManager, "execute");

      reportService.updateDoingToFailedWithLock(queryRunner);
      expect(spy.calledOnce).be.true;
    });
  });

Any help is welcome. Thanks in advance!


Solution

  • I saw your code and there's something can be improved:

    • Use returnsThis() to replace return fakeManager
    • Don't forget await when calling updateDoingToFailedWithLock
    describe("updateDoingToFailedWithLock()", (): void => {
      let sandbox: sinon.SinonSandbox;
    
      beforeEach(() => (sandbox = sinon.createSandbox()));
      afterEach(() => sandbox.restore);
    
      it("should be success", async (): Promise<void> => {
    
        // using returnsThis()
        const fakeManager = {
          getRepository: sandbox.stub().returnsThis(),
          createQueryBuilder: sandbox.stub().returnsThis(),
          useTransaction: sandbox.stub().returnsThis(),
          setLock: sandbox.stub().returnsThis(),
          update: sandbox.stub().returnsThis(),
          set: sandbox.stub().returnsThis(),
          where: sandbox.stub().returnsThis(),
          execute: sandbox.stub().returnsThis(),      
        }
    
        const fakeQueryRunner = {
          manager: fakeManager,
        };
    
        const reportService = new ReportService();
    
        // having await here is important
        await reportService.updateDoingToFailedWithLock(fakeQueryRunner);
    
    
        expect(fakeManager.execute.calledOnce).to.be.true;
        expect(fakeManager.createQueryBuilder.calledWith('report')).to.be.true;
      });
    });
    

    Hope it helps