What is the best way to imitate an imported class definition using sinon?

Having trouble mocking an import in my spec file and seeking help to identify the issue.

This is the exported class for the Database connection:

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

This is the action file attempting to use the database file:

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

And here is the test file:

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

      // The stub doesn't seem to be called. Import not replaced with stub in 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);
    });
  });
});

Facing issues where the import is not being replaced with the stub in the implementation file during tests.

Answer №1

It's important to note two key considerations:

  1. When importing the db from the database.ts file in both the test file and GraphQL resolver file, there are distinct instances created. This means that even if you stub the methods of the db instance in the test file, the resolver will still utilize the original methods (not the stubbed ones), posing risks during testing.

  2. The recommended approach for using dependencies within a GraphQL resolver is to pass the dependencies (such as the db instance) through the resolver context argument. Implementing this kind of Dependency Injection not only simplifies testing but also minimizes potential issues.

For example:

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

No need to import and stub db. Instead, create a stub db and pass it directly to the resolver context.

Unit test results with coverage report:

  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 |
----------------|----------|----------|----------|----------|-------------------|

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Why is there a discrepancy between the value displayed in a console.log on keydown and the value assigned to an object?

As I type into a text box and log the keydown event in Chrome, I notice that it has various properties (specifically, I'm interested in accessing KeyboardEvent.code). Console Log Output { altKey: false bubbles: true cancelBubble: false cancelable: t ...

"Facing a challenge with Angular 2 where an HTTP request is being triggered twice

After thorough research on Stack Overflow, I can confidently say that the issue is not with my code. However, my REST APIs are being called twice. Here is a snippet of my code: Component: export class Component { constructor(private _nServ ...

Creating a function that is accessible to the entire module

Creating a universal function in a file that is not a module: example.ts: function example() {} You can easily call this function from another file, say test.ts, without needing to import the function from example.ts: test.ts: example(); // calling univ ...

Simplify the cognitive load of the function

I'm facing a challenge with a function that has a cognitive complexity of 24, however, we have a requirement to limit it to a maximum of 15. export function parameterValueParser<T extends Structure>( structure: T ): (parameterValue: Value) =&g ...

Child class in TypeScript lacking type information for abstract property

Having an issue with my TypeScript 3.4 code that seems a bit strange. Here's a snippet of the problematic code: interface MyInterface { fn: (x: number) => number; } abstract class A { abstract prop: MyInterface; } class B extends A { prop ...

The revalidation process in react-hook-form doesn't seem to initiate

Stumbled upon a code example here Decided to fork a sandbox version (original had bugs and errors) I am trying to implement custom validation callbacks for each form input element by providing options in the register function, but the validate is only tr ...

Issue with music in Phaser3 game not playing correctly when transitioning to a new scene

I'm completely new to Phaser and I've encountered an issue with adding a start menu to my main platformer scene. The gameplay scene plays music seamlessly, but when I introduce a start menu, things start to go wrong. Here's a snippet of the ...

Storing data from a collection of interface objects in a string array

Take a look at the following code snippet: import React, {FC} from 'react'; import {useFetchErrors} from "../Api/Api"; import {useLocation} from "react-router-dom"; interface ExecutionTableProps { project_id: number } const ...

Error TS2339: The attribute 'scope' is not found in the 'JwtPayload' data type

Encountering an error in my IntelliJ IDE while trying to utilize decodedJwt.scope. The specific error message reads: Property 'scope' is not available on type 'JwtPayload'. Suggestions offered by IntelliJ include: sub, aud, exp, iat, ...

Learn how to bring a component into another component within Angular

I have developed a component named CopySchedulefromSiteComponent and now I am looking to import it into another component called SiteScheduleComponent. However, I am unsure of the correct way to do this. The CopySchedulefromSiteComponent contains one fiel ...

Struggling to increment the badge counter in a React Typescript project

I'm a bit unsure about where to apply the count in my Badge Component. The displayValue should include the count, but I'm struggling with how to implement it. UPDATE: The counter is now in place, but when I decrease the count and it reaches 0, t ...

Encountering an issue when trying to generate a button in Angular

I am currently using JavaScript to dynamically create a button in Angular. While I have been successful in creating the button, I am encountering an error when attempting to change the classname. The error message I am receiving is: Property 'clas ...

Showing the state on a different page: A step-by-step guide

I'm currently in the process of creating a model for a real estate website. On the homepage, users can choose between 'rent' or 'purchase' using a select option and view the results on that page. I have successfully stored the sear ...

When accessing a method exposed in Angular2 from an external application, the binding changes are lost

In my code, I have a method that is made public and accessible through the window object. This method interacts with a Component and updates a variable in the template. However, even after changing the value of the variable, the *ngIf() directive does not ...

How can I reduce unnecessary spacing in a primeNg Dropdown (p-dropdown) filter within an Angular 5 application?

In my Angular 5 project, I have implemented PrimeNG dropdown (p-dropdown) and encountered an issue. When I try to filter the dropdown data by adding spaces before and after the search term, it displays a No Results Found message. How can I fix this problem ...

styles.css is generating an ERROR message, indicating that it is unable to read properties of null when trying to access the 'classList'

Hey there, I've been working on adding a background color change to my navbar when the scrollY exceeds 30px, and it's functioning properly. However, I'm encountering an error in the console which states that the new classList cannot be added ...

What is the best way to incorporate CSS from node_modules into Vite for production?

I have a TypeScript web application where I need to include CSS files from NPM dependencies in index.html. Here is an example of how it is done: <link rel="stylesheet" type="text/css" href="./node_modules/notyf/notyf.min.css&quo ...

"Encountering issues when trying to retrieve a global variable in TypeScript

Currently facing an issue with my code. I declared the markers variable inside a class to make it global and accessible throughout the class. However, I am able to access markers inside initMap but encountering difficulties accessing it within the function ...

What are some examples of utilizing paths within the tsconfig.json file?

Exploring the concept of path-mapping within the tsconfig.json file led me to the idea of utilizing it to streamline cumbersome path references: https://i.sstatic.net/AYmv4.png The project layout is unconventional due to its placement in a mono-repositor ...

Angular Kendo UI offers the ability to customize the way grid columns are sorted

I'm currently working on a Kendo Grid (UI for Jquery) where I have implemented a custom sort method for a specific column based on my customers' requirements. field: "daysLeft", title: "Accessible", width: 130, sortable: { compare: function (a ...