Tips for mock nesting a repository in TypeORM?

I'm struggling to figure out how to stub a nested Repository in TypeORM. Can anyone assist me in creating a sinon stub for the code snippet below? I attempted to use some code from another Stack Overflow post in my test file, but it's not working as expected. The line of code I need to stub is:

const project = await getManager().getRepository(Project).find({ where: queryParams });

Here is the relevant source code:

project.controller.ts

//getConnection and UpdateResult are not utilized in this function
import {
  EntityManager,
  getConnection,
  getManager,
  UpdateResult,
} from "typeorm";
import { Project } from "./project.entity";
import { validate } from "class-validator";
import logger from "../../utils/logger";
export const findOneProject = async (queryParams: {}): Promise<
  Project
> => {
  try {
    const project = await getManager().getRepository(Project).find({
      where: queryParams,
    });
    if (project.length > 1) {
      throw new Error("Found more than one project");
    }
    return project[0];
  } catch (e) {
    throw new Error(`Unable to find project: ${e.message}`);
  }
};

project.test.ts

import sinon from "sinon";
import {  findOneProject } from "./project.controller";
import { Project } from "./project.entity";
import { EntityManager, Repository } from "typeorm";

const mockProject = {
  ID: "insert-id-here",
  name: "name",
};

describe("Testing findOneProject", () => {
  it("Should return the same project", () => {
    const sandbox = sinon.createSandbox();
    // I believe I can stub the repository, but I am unsure how to stub the find() method
    sandbox.stub(EntityManager.prototype, "get").returns({
      getRepository: sandbox
        .stub()
        .returns(sinon.createStubInstance(Repository)),
    });
    const project = await findOneProject(ID: "insert-id-here");
    expect(project).toBe(mockProject);
  });
});

Appreciate any help on this!

Edit: I have provided additional details in the test file for clarity.

Answer №1

If you want to implement Link Seams with CommonJS, then consider using proxyquire to create your seams.

For example:

project.controller.ts:

import { getManager } from 'typeorm';
import { Project } from './project.entity';

export const findOneProject = async (queryParams: {}): Promise<Project> => {
  try {
    const project = await getManager()
      .getRepository(Project)
      .find({
        where: queryParams,
      });
    if (project.length > 1) {
      throw new Error('Found more than one project');
    }
    return project[0];
  } catch (e) {
    throw new Error(`Unable to find project: ${e.message}`);
  }
};

project.entity.ts:

export class Project {
  // implementation
}

project.controller.test.ts:

import sinon from 'sinon';
import chai, { expect } from 'chai';
import proxyquire from 'proxyquire';
import { Project } from './project.entity';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);

const mockProject = {
  ID: 'insert-id-here',
  name: 'name',
};

describe('Testing findOneProject', () => {
  let sandbox: sinon.SinonSandbox;
  before(() => {
    sandbox = sinon.createSandbox();
  });
  it('Should return the same project', async () => {
    const typeormStub = {
      getManager: sandbox.stub().returnsThis(),
      getRepository: sandbox.stub().returnsThis(),
      find: sandbox.stub().resolves([mockProject]),
    };
    const { findOneProject } = proxyquire('./project.controller', {
      typeorm: typeormStub,
    });

    const project = await findOneProject({ id: 1 });
    sandbox.assert.calledOnce(typeormStub.getManager);
    sandbox.assert.calledWithExactly(typeormStub.getRepository, Project);
    sandbox.assert.calledWithExactly(typeormStub.find, { where: { id: 1 } });
    expect(project).to.be.eq(mockProject);
  });

  it('should throw error if found more than one project', async () => {
    const typeormStub = {
      getManager: sandbox.stub().returnsThis(),
      getRepository: sandbox.stub().returnsThis(),
      find: sandbox.stub().resolves([{ name: 'a' }, { name: 'b' }]),
    };
    const { findOneProject } = proxyquire('./project.controller', {
      typeorm: typeormStub,
    });

    await expect(findOneProject({ id: 1 })).to.be.rejectedWith('Found more than one project');
    sandbox.assert.calledOnce(typeormStub.getManager);
    sandbox.assert.calledWithExactly(typeormStub.getRepository, Project);
    sandbox.assert.calledWithExactly(typeormStub.find, { where: { id: 1 } });
  });

  it('should throw error if find project error', async () => {
    const typeormStub = {
      getManager: sandbox.stub().returnsThis(),
      getRepository: sandbox.stub().returnsThis(),
      find: sandbox.stub().rejects(new Error('timeout')),
    };
    const { findOneProject } = proxyquire('./project.controller', {
      typeorm: typeormStub,
    });

    await expect(findOneProject({ id: 1 })).to.be.rejectedWith('Unable to find project: timeout');
    sandbox.assert.calledOnce(typeormStub.getManager);
    sandbox.assert.calledWithExactly(typeormStub.getRepository, Project);
    sandbox.assert.calledWithExactly(typeormStub.find, { where: { id: 1 } });
  });
});

unit test result:

  Testing findOneProject
    ✓ Should return the same project (2395ms)
    ✓ should throw error if found more than one project
    ✓ should throw error if find project error


  3 passing (2s)

-----------------------|---------|----------|---------|---------|-------------------
File                   | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------------------|---------|----------|---------|---------|-------------------
All files              |     100 |      100 |     100 |     100 |                   
 project.controller.ts |     100 |      100 |     100 |     100 |                   
 project.entity.ts     |     100 |      100 |     100 |     100 |                   
-----------------------
 

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

When using Framer Motion for page transitions alongside React Router DOM v6, the layout components, particularly the Sidebar, experience rerenders when changing pages

After implementing page transitions in my React app using Framer Motion and React-Router-DOM, I noticed that all layout components such as the sidebar and navbar were unexpectedly rerendering upon page change. Here's a snippet of my router and layout ...

The ESLint setup specified in the package.json file for eslint-config-react-app is deemed to be incorrect

The property named "overrides" has the incorrect type (expected array but received {"files":["**/*.ts","**/*.tsx"],"parser":"@typescript-eslint/parser","parserOptions":{"ecmaVersion":2018,"sourceType":"module","ecmaFeatures":{"jsx":true},"warnOnUnsupported ...

Tips for differentiating between elements with identical values in an HTML datalist using Angular

My boss is insisting that I use a datalist in our website interface to select an employee, even though there's no way to determine if the user typed in the name or picked from the list. The challenge is that the list must only display full names, but ...

Ways to efficiently update the API_BASE_URL in a TypeScript Angular client generated by NSwag

Is it possible to dynamically change the API_BASE_URL set in my TypeScript client generated by NSWAG? I want to be able to utilize the same client with different API_BASE_URLs in separate Angular modules. Is this achievable? Thank you for your assistance. ...

The Unusual Behavior of Typescript Partial Interfaces

While reviewing the code in a repository I am currently working on, I stumbled upon something that seemed completely incorrect. Here is a snippet of what caught my attention. interface Car { make: string model: string } type SomeType = Partial<Car& ...

Guidelines for implementing more rigorous type checks in TypeScript:

I am looking to enhance the code validation process and eliminate any implicit 'any' types. To achieve this, I have set "strict": true in my tsconfig.json. { "compilerOptions": { "target": "ES5", ...

Asynchronous execution of Angular 2 services

Currently, I am working on a project that utilizes Angular and RxJS. My approach involves creating an injectable class responsible for fetching data from a JSON source as shown below: import {Injectable, Inject} from '@angular/core'; import {Ht ...

What is the significance of utilizing an empty value `[]` for a typed array interface instead of using an empty `{}` for a typed object interface?

Why can I initialize friends below as an empty array [], but not do the same for session with an empty object {}? Is there a way to use the empty object without needing to make all keys optional in the interface? const initialState: { friends: Array< ...

When you hover over the button, it seamlessly transitions to a

Previously, my button component was styled like this and it functioned properly: <Button component={Link} to={link} style={{ background: '#6c74cc', borderRadius: 3, border: 0, color: 'white', height: 48, padding: '0 ...

The Angular 2 router UMD file, router.umd.js, was not found

Trying to run an Angular 2 project and implement @angular/router is proving to be a bit challenging. Everything seems to be working fine, until the moment I attempt: import { provideRouter, RouterConfig } from '@angular/router'; As it tries to ...

What is the correct way to link an array with ngModel using ngFor loop in Angular?

Utilizing ngModel within an ngFor iteration to extract values from a single input field like this : <mat-card class="hours" > <table id="customers"> <thead > <th >Project</th> ...

How can I toggle the visibility of a div after the DOM has finished loading?

I was experimenting with a radio button on the interface linked to a property in the typescript file that controls the visibility of another div. However, I noticed that both *ngIf="isFooSelected" and [hidden]="!isFooSelected" only function upon initial pa ...

Ways to sequentially execute API calls rather than concurrently

Update: Find the complete solution at the end of this answer. Consider the following code snippet: @Injectable() export class FileUploader { constructor(private http: Http) {} upload(url: string, file: File) { let fileReader: FileReader ...

Downcasting on "this" is not supported in Typescript

In the example below, the TypeScript compiler does not allow for a direct cast of this to Child. However, it is possible to achieve this using an intermediate variable like 'temp' or double casting as shown in the commented lines. Is this behavio ...

Angular interceptors in sequence

I'm looking to implement a queue system in Angular. My goal is to store requests in an array and process them sequentially, moving on to the next request once the current one is successful. Below is the code I tried, but unfortunately it didn't ...

Issue: The function (0, react__WEBPACK_IMPORTED_MODULE_1__.useActionState) is not recognized as a valid function or its output is not iterable

I found a great example of using useActionState at this source. Currently, I am implementing it in my project with Next.js and TypeScript. app/page.tsx: "use client"; import { useActionState } from "react"; import { createUser } from ...

Allow Visual Studio Code to create a constructor for Typescript class

When developing Angular 2 apps in Typescript using Visual Studio Code, one common task is writing constructors with their parameter list. Is there a way to save time and effort on this? It would be really helpful if the IDE could automatically generate th ...

How can I use TypeScript to wrap a component in Vue 3?

Looking to customize a PrimeVue component (Calendar) by styling it differently and then re-exporting it. Here's an example in React: const WrappedCalendar: React.FC<CalendarProps> = (props)=> <div style={{background:'green'}}&g ...

Dynamically loading external JavaScript for an Angular component and triggering the window load event

I am currently dealing with an external javascript file that I only want to be included on a specific component, so the approach I'm taking involves dynamically loading it. I came across this answer that explains exactly how to achieve this. The prob ...

Join a subscription and remain subscribed in sequential order

Within the code below, there is a nested subscribe function. It takes a schedule_id and retrieves questions based on that schedule_id. The functionality works correctly, but the order in which getQuestion() is executed is not guaranteed. Schedule IDs: 111, ...