【问题标题】:How do I mock an imported class definition with sinon如何使用 sinon 模拟导入的类定义
【发布时间】:2020-01-14 00:39:07
【问题描述】:

我似乎无法在我的规范文件中正确模拟导入,我想知道是否有人能看到我缺少的内容。

这是我的数据库连接的导出类

import Knex from 'knex';
import { merge } from 'lodash';
import knexfile from '../knexfile';

class Database {
  private knexInstance: Knex;
  private config: object;

  connect(options = {}): void {
    if (this.knexInstance) {
      return;
    }
    this.config = merge({}, knexfile, options);
    this.knexInstance = Knex(this.config);
  }

  get query(): Knex {
    if (!this.knexInstance) {
      this.connect();
    }

    return this.knexInstance;
  }

  close(done): void {
    if (!this.knexInstance) {
      done();
      return;
    }

    this.knexInstance.destroy(done);
  }
}

export default new Database();

这是尝试使用数据库文件的操作文件。

import db from '../../database';
const tableName = 'attempts';

export const typeDef = `
  extend type Query {
    attempt(id: String): Attempt!
  }

  extend type Mutation {
    createAttempt(questionId: String!, attemptId: String!, choiceId: String): Attempt
  }

  type Attempt {
    id: String!
    correctanswers: Int!
    userid: String!
    examid: String!
  }
`;

export const resolvers = {
  Query: {
    attempt(_, { id = '' }) {
      return db
        .query(tableName)
        .where({ id })
        .first();
    },
  },
  Mutation: {
    async createAttempt(root, args) {
      const [answer] = await db
        .query(tableName)
        .insert(args)
        .returning('*');

      return answer;
    },
  },
};

这是我的测试文件。

import { createSandbox } from 'sinon';
import { resolvers } from './answer';
import db from '../../database';
import * as should from 'should';

const sandbox = createSandbox();

describe('Answer', () => {
  afterEach(() => sandbox.restore());

  describe('Query Answer', () => {
    it('should return answer by id', async () => {
      const expected = { id: 'xxx' };
      const firstSpy = sandbox.fake.resolves(expected);
      const whereSpy = sandbox.fake.resolves({
        first: firstSpy,
      });

      // This stub never seems to get called. It doesn't look like the import is ever being replaced with the stub in the implementation file.
      const querySpy = sandbox.stub(db, 'query').callsFake(() => {
        return Promise.resolve({
          where: whereSpy,
        });
      });
      const inputId = '100';

      const result = await resolvers.Query.answer(null, { id: inputId });
      sandbox.assert.calledOnce(querySpy);
      sandbox.assert.calledOnce(whereSpy);
      sandbox.assert.calledOnce(firstSpy);
      result.should.deepEqual(expected);
    });
  });
});

当我运行测试时,看起来导入并没有被实现文件中的存根替换,我不明白为什么。

【问题讨论】:

    标签: typescript testing mocha.js sinon


    【解决方案1】:

    有两个注意事项:

    1. 当您从测试文件中的 database.ts 文件和 GraphQL 解析器文件中导入 db 时,它们是不同的实例。因此,即使您在测试文件中存根 db 实例的方法。解析器仍然使用 db 实例和原始方法(不是存根)。测试存在潜在风险。

    2. 在 GraphQL 解析器中使用依赖项的最佳实践是根据解析器 context 参数传递依赖项(您的案例中的 db 实例)。因为它是某种依赖注入,它使代码更容易测试。

    例如

    answer.ts:

    const tableName = "attempts";
    
    export const typeDef = `
      extend type Query {
        attempt(id: String): Attempt!
      }
    
      extend type Mutation {
        createAttempt(questionId: String!, attemptId: String!, choiceId: String): Attempt
      }
    
      type Attempt {
        id: String!
        correctanswers: Int!
        userid: String!
        examid: String!
      }
    `;
    
    export const resolvers = {
      Query: {
        attempt(_, { id = "" }, { db }) {
          return db
            .query(tableName)
            .where({ id })
            .first();
        },
      },
      Mutation: {
        async createAttempt(root, args, { db }) {
          const [answer] = await db
            .query(tableName)
            .insert(args)
            .returning("*");
    
          return answer;
        },
      },
    };
    

    anwser.test.ts:

    import sinon from "sinon";
    import { resolvers } from "./answer";
    import { expect } from "chai";
    
    describe("Answer", () => {
      describe("Query Answer", () => {
        it("should return answer by id", async () => {
          const expected = { id: "xxx" };
          const inputId = "100";
    
          const knexInstanceStub = {
            query: sinon.stub().returnsThis(),
            where: sinon.stub().returnsThis(),
            first: sinon.stub().resolves(expected),
          };
    
          const result = await resolvers.Query.attempt(null, { id: inputId }, { db: knexInstanceStub });
          sinon.assert.calledOnce(knexInstanceStub.query);
          sinon.assert.calledOnce(knexInstanceStub.where);
          sinon.assert.calledOnce(knexInstanceStub.first);
          expect(result).to.be.deep.eq(expected);
        });
      });
    });
    

    我们甚至不需要导入db 并存根它。我们可以创建一个存根db 并将其传递给解析器的上下文。

    带有覆盖率报告的单元测试结果:

      Answer
        Query Answer
          ✓ should return answer by id
    
    
      1 passing (11ms)
    
    ----------------|----------|----------|----------|----------|-------------------|
    File            |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ----------------|----------|----------|----------|----------|-------------------|
    All files       |    84.62 |    33.33 |       80 |    86.36 |                   |
     answer.test.ts |      100 |      100 |      100 |      100 |                   |
     answer.ts      |       60 |    33.33 |       50 |     62.5 |          30,31,36 |
    ----------------|----------|----------|----------|----------|-------------------|
    

    【讨论】:

    • 感谢您的回答!这就像一个魅力。我实际上是通过上下文传递数据加载器的东西,所以我不知道为什么我没有想到对数据库连接做同样的事情。再次感谢伙计!
    猜你喜欢
    • 2021-09-22
    • 2021-09-13
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 2023-03-20
    • 2019-07-22
    • 2018-08-28
    • 1970-01-01
    相关资源
    最近更新 更多