Как мне издеваться над импортированным определением класса с помощью sinon - PullRequest
0 голосов
/ 14 января 2020

Кажется, я не могу правильно смоделировать импорт в моем файле spe c, и мне интересно, может ли кто-нибудь увидеть, что мне не хватает.

Вот экспортированный класс для моего подключения к базе данных

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);
    });
  });
});

Когда я запускаю проверяет, не похоже ли это, что импорт когда-либо заменяется заглушкой в ​​файле реализации, и я не понимаю, почему.

1 Ответ

1 голос
/ 14 января 2020

Существует два предупреждения:

  1. Это разные случаи, когда вы импортируете файл db из database.ts в тестовый файл и файл распознавателя GraphQL. Так что, даже если вы заглушите методы экземпляра db в тестовом файле. Средство распознавания все еще использует экземпляр db с исходными методами (не заглушенными). Есть потенциальные риски для тестирования.

  2. Лучшая практика использования зависимостей в решателе GraphQL - передавать зависимости (экземпляр db для вашего случая) в соответствии с аргументом решателя context. Поскольку это своего рода внедрение зависимостей, оно облегчает тестирование кода.

Например

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 |
----------------|----------|----------|----------|----------|-------------------|
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...