How can we effectively test the document.save() function in NestJS using Mongoose?

My approach involves using some abstractions where the code receives a User, converts it to Mongo format (adding an underscore to the id generated elsewhere), saves it, and then returns the saved User without the underscore in the id:

  constructor(
    @InjectModel('User')
    private readonly service: typeof Model
  ) { }

  async saveUser(user: User): Promise<User> {
    const mongoUser = this.getMongoUser(user);
    const savedMongoUser = await new this.service(mongoUser).save();
    return this.toUserFormat(savedMongoUser);
  }

The test scenario I'm working on:

  beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            MongoUserRepository,
            {
              provide: getModelToken('User'),
              useValue: { ... }, // all functions used with jest.fn()
            },
          ],
        }).compile();
    service = module.get<MongoUserRepository>(MongoUserRepository);
    model = module.get<Model<UserDocument>>(getModelToken('User'));
  });

  it('should save a new user', async () => {
    jest.spyOn(model, 'save').mockReturnValue({
      save: jest.fn().mockResolvedValueOnce(mockMongoFormat)
    } as any);

    const foundMock = await service.saveUser(mockUserFormat);
    expect(foundMock).toEqual(mockUserFormat);
  });

The problems encountered:

An error stating that no overload matches this call.
  Option 1 of 4, '(object: Model<UserDocument, {}>, method: "model" | "remove" | "deleteOne" | "init" | "populate" | "replaceOne" | "update" | "updateOne" | "addListener" | "on" | ... 45 more ... | "where"): SpyInstance<...>', triggered this issue.
    The argument '"save"' is not suitable for the parameter '"model" | "remove" | "deleteOne" | "init" | "populate" | "replaceOne" | "update" | "updateOne" | "addListener" | "on" | "once" | "removeListener" | "off" | "removeAllListeners" | ... 41 more ... | "where"'.
  Option 2 of 4, '(object: Model<UserDocument, {}>, method: "collection"): SpyInstance<Collection, [name: string, conn: Connection, opts?: any]>', resulted in this error.
    The argument '"save"' is not suitable for the parameter '"collection"'.ts(2769)

Attempts to use "new" also faced challenges:

An error indicating that no overload matches this call.
  Option 1 of 4, '(object: Model<UserDocument, {}>, method: "find" | "watch" | "translateAliases" | "bulkWrite" | "model" | "$where" | "aggregate" | "count" | "countDocuments" | ... 46 more ... | "eventNames"): SpyInstance<...>', triggered this problem.
    The argument '"new"' is not suitable for the parameter '"find" | "watch" | "translateAliases" | "bulkWrite" | "model" | "$where" | "aggregate" | "count" | "countDocuments" | "estimatedDocumentCount" | "create" | "createCollection" | ... 43 more ... | "eventNames"'.
  Option 2 of 4, '(object: Model<UserDocument, {}>, method: "collection"): SpyInstance<Collection, [name: string, conn: Connection, opts?: any]>', resulted in this error.
    The argument '"new"' is not suitable for the parameter '"collection"'.

I might need to reconsider my implementation, but I am keen on discovering how to proceed in this situation. How can I effectively mock that function?

Answer №1

After taking a break and returning, I implemented the following solution:

async storeUserRecord ( user: User ): Promise<User> {
    const mongoUser = this.convertToMongoFormat(user);
    const savedMongoUser = await this.userService.create( mongoUser );
    return this.prepareForUser(savedMongoUser);
}

I swapped out 'new userService(doc).save()' with 'userService.create(doc)'

The passing test now looks like this:

  it( 'should successfully store a new user', async () => {
    jest.spyOn( databaseModel, 'create' ).mockImplementation(
      jest.fn().mockResolvedValueOnce( mockMongoData )
    );
    const result = await userService.storeUserRecord( mockUserData );
    expect( result ).toEqual( mockUserData );
  } );

And that's how we achieved full code coverage. Great job! https://i.sstatic.net/XfAJx.png

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

Does the value of an Angular reactive form control only reflect what the user inputs?

I am working with a reactive form where I need to extract values and assign them to a variable defined in an interface. The form is populated using the Google Places API, which disables user interaction with all controls except for the initial address inpu ...

Creating a canvas that adjusts proportionally to different screen sizes

Currently, I am developing a pong game using Angular for the frontend, and the game is displayed inside an HTML canvas. Check out the HTML code below: <div style="height: 70%; width: 70%;" align="center"> <canvas id=&q ...

Adding elements to an array within an object utilizing Mongoose

Having trouble updating the posts array in my data object. Here is the schema I'm working with: const mongoose = require('mongoose'); const Post = require('./post'); const Review = require('./comment') const User = ...

Having trouble sending props

After successfully writing Jest tests for a React site I created 6 months ago, I decided to work on a new project based off the same codebase. However, when I attempted to write similar tests for basic component rendering, they failed unexpectedly. The er ...

When attempting to parse a file name using a regular expression in TypeScript (or even plain Node.js), the unexpected outcome is a

Looking to extract language information from a filename? Check out this simple construct: The structure of my language.ts model is as follows: export interface Language { language?: string; region?: string; } The function designed for parsing the fi ...

Navigating Dynamically between tabs - A How-to Guide

I am working on a mat-tab Angular app where I need to dynamically generate links and transfer them to a navLinks object. Despite ensuring that the concatenation is correct, it seems like my approach is not working as expected. Here's a glimpse of what ...

What is the best way to define a function that accepts an object with a specific key, while also allowing for any additional keys to be passed

Typescript allows us to define an interface for an object that must have a key and can also allow additional keys: interface ObjectWithTrace { trace: string; [index: string]: any } const traced: ObjectWithTrace = { trace: 'x', foo: 'bar ...

Exploring and Monitoring a Target in an Angular Module: Jasmine Investigation

My component utilizes a Subject<void>, which triggers another function - refresh() upon emission. It is crucial for my unit tests to validate this behavior, specifically checking if the refresh() function is invoked. Referring to the Jasmine documen ...

At what point are routed components initialized?

Here is a route setup I am working with: path: ':id', component: ViewBookPageComponent }, After adding this route, an error keeps popping up: Error: Cannot read property 'id' of null I haven't included a null check in the compo ...

Total number of requests made since the previous reset

I'm currently working on developing an API and I need to set up a route like api/v1/status in order to check the server status. This route should return a JSON response with the total number of requests made to the API since it became active. However, ...

"Sequencing http.get requests in Angular 2 using

In my service, I have a series of http.get requests structured as follows: constructor(private http:Http) {} getDetails(sysID:string){ var details; this.http.get('https://blahURL').map(res => res.json().filter(f => f.id == another.id)[0] ...

The issue encountered is when the data from the Angular form in the login.component.html file fails to be

I am struggling with a basic login form in my Angular project. Whenever I try to submit the form data to login.components.ts, it appears empty. Here is my login.component.html: <mat-spinner *ngIf="isLoading"></mat-spinner> & ...

Utilizing a variable as a condition in the results

Currently, I am utilizing this Moongose query to work with geo-spatial data: Locations.find({ loc: { $geoWithin: { $centerSphere: [[lng, lat], radius / 6378.1], }, } }, cb); While it functions effectively, I am curious ...

Using Textarea for Linebreaks with ExpressJS, MongooseJS, and Jade

Within my Jade code, I have a textarea located inside a form. form(action='save') textarea(name='description') input(type='submit') The description entered is then saved to mongodb using the mongoosejs library. desc ...

Assigning the accessToken directly to Mapbox results in a permanent error stating that Imports cannot be changed

Setting accessToken directly to mapboxgl results in an error. In JavaScript, imports are immutable. To change the value of this import, you need to export a setter function in the imported file (such as "setAccessToken") and then import and call that f ...

Different Types of Props for Custom Input Components using React Hook Form

Struggling with creating a custom FormInput component using React Hook Form and defining types for it. When calling my component, I want to maintain autocompletion on the name property like this ... <FormInput control={control} name={"name"}& ...

Ways to implement debounce in handling onChange events for input fields in React

I've been attempting to implement debounce functionality in my React app without relying on external libraries like lodash or third-party node modules. I've tried various solutions found online, but none have worked for me. Essentially, in the h ...

Tips for setting the `mode` property in typeScript with VueRouter

I am a TypeScript novice and encountering a problem: export default function ({ store }) { const Router = new VueRouter({ scrollBehavior: () => ({ x: 0, y: 0 }), routes, // Keep these settings in quasar.conf.js and modify there instead! ...

Implementing various functions on a Mongoose Model Object

Using Nodejs Expressjs MongoDB and Mongoose, I am developing a REST API for a small service app. All routes are created with simple functions like .find() or .findOneAndUpdate(). For example: router.get('/testTable', function(req, res, next) { ...

Issue with Parent.save() not executing as expected when a sub document or deeply nested document is updated

After searching through my entire document collection, I found the specific piece of code that I needed: const account = await Account.findOne({ "buildings.gateways.devices.verificationCode": code }) const buildings = account.buildings const ...