nest: Unable to run tests because Nest can't resolve dependencies of a service

I’m submitting a…


[ ] Regression 
[ ] Bug report
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

I have followed the unit test example but I am unable to get my very simple test to run (it’s literally just testing if true === true) but it won’t work because I’m met with this error

Nest can't resolve dependencies of the RoleService (?). Please verify whether [0] argument is available in the current context.

Minimal reproduction of the problem with instructions

You can find the repo at https://bitbucket.org/mogusbi/breeze-bb/

After cloning, run npm run api:test

// role.controller.spec.ts
import {Test} from '@nestjs/testing';
import {TestingModule} from '@nestjs/testing/testing-module';
import {RoleController} from './role.controller';
import {RoleService} from './role.service';

describe('Role controller', () => {
  let controller: RoleController;
  let service: RoleService;

  beforeEach(async () => {
    const mod: TestingModule = await Test
      .createTestingModule({
        components: [
          RoleService
        ],
        controllers: [
          RoleController
        ]
      })
      .compile();

    controller = mod.get<RoleController>(RoleController);
    service = mod.get<RoleService>(RoleService);
  });

  it('should be true', () => {
    expect(true).toBe(true);
  });
});

// role.controller.ts
import {Controller} from '@nestjs/common';
import {RoleService} from './role.service';

@Controller('role')
export class RoleController {
  constructor (
    private readonly roleService: RoleService
  ) {}

  ...
}

// role.service.ts
import {InjectModel} from '@nestjs/mongoose';
import {PaginateModel} from 'mongoose';
import {IRole} from './role.interface';
import {RoleSchema} from './role.schema';

@Component()
export class RoleService {
  constructor (
    @InjectModel(RoleSchema) private readonly model: PaginateModel<IRole>
  ) {}

  ...
}

Environment


- Node version: 8.2.1
- Platform:  Mac OS 10.13.2

├── @nestjs/common@4.5.9
├── @nestjs/core@4.5.9
├── @nestjs/mongoose@2.0.0
├── @nestjs/swagger@1.1.3
├── @nestjs/testing@4.5.5
├── @types/body-parser@1.16.8
├── @types/express@4.11.0
├── @types/jest@22.0.1
├── @types/mongoose@4.7.32
├── @types/mongoose-paginate@5.0.6
├── @types/morgan@1.7.35
├── @types/node@8.5.8
├── body-parser@1.18.2
├── class-transformer@0.1.8
├── class-validator@0.7.3
├── jest@22.0.6
├── mongoose@4.13.9
├── mongoose-paginate@5.0.3
├── morgan@1.9.0
├── nodemon@1.14.11
├── reflect-metadata@0.1.10
├── rxjs@5.5.6
├── ts-jest@22.0.1
├── ts-node@4.1.0
├── tslint@5.9.1
├── tslint-eslint-rules@4.1.1
├── tslint-microsoft-contrib@5.0.1
└── typescript@2.6.2

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 5
  • Comments: 54 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Hey guys!

I’ve read the testing part of the database documentation but I could not find any hint or best practice for creating the mockRepository object.

I would like to write tests where I mock the database layer of the service (e.g.: the adminRepository of the AuthService) by providing good ol’ POJOs as data source. Is this achievable with the current version of TypeORM / Nest?

There is a related conversation in typeorm/typeorm#1267, but I couldn’t get closer to my desired functionality.

My AuthService looks like this:

// auth.service.ts

@Injectable()
export class AuthService {

  constructor(
    @InjectRepository(Admin)
    private readonly adminRepository: Repository<Admin>,
  ) { }

  findByEmail(email: string): Promise<Admin> {
    return this.adminRepository.findOne({ where: { email } });
  }

  findById(id: number): Promise<Admin> {
    return this.adminRepository.findOne(id);
  }

}

The desired output would look like something like this:

// auth.service.spec.ts

describe('AuthService', () => {
  let authService: AuthService;

  const mockRepository = {
    data: [
      { id: 1, email: 'test1@email.com', password: '' },
      { id: 2, email: 'valid@email.com', password: '' },
    ],
  };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
        providers: [
          AuthService,
          {
            provide: getRepositoryToken(Admin),
            useValue: mockRepository,
          },
        ],
      }).compile();

    authService = module.get<AuthService>(AuthService);
  });

  describe('findByEmail', () => {
    it('should return an Admin with a valid email', async () => {
      const email = 'valid@email.com';
      const expected = mockRepository.data[1];
      const admin = await authService.findByEmail(email);
      expect(admin).toBe(expected);
    });
  });

});

I really look forward to hear some advices and tips about the pros/cons of this kind of testing strategy, my primary goal is to keep my unit tests as simple and as fast as I can. I really appreciate any help!

For people encoutering the same error message with TypeORM, please note that the DI loader cannot “know” your Repository instances as they’re injected with @InjectRepository:

export class DogsService {
  constructor(@InjectRepository(Dog) private readonly dogsRepository: Repository<Dog>) {
    // ...
  }
}

I had more or less the same issue this morning and solved it by injecting “DogsRepository” manually:

Test.createTestingModule({
  components: [
    {
      provide: 'DogsRepository',
      useClass: Repository,
    }
  ],
  // ...
}).compile();

However, I would recommend extending Repository<Dog> with a custom repo Class (class DogsRepository extends Repository<Dog>) and use it in useClass (useClass: DogsRepository, instead of useClass: Repository), in order to be able to test the type of values returned by the repository instance (if you need to).

@kamilmysliwiec: does everything sound correct? Or am I mistaken somewhere? Shall I add it do the documentation?

In the documentation “Mongoose.forRoot” is imported in the Application module, this module is not imported during the creation the TestModule leading to this error as Mongoose.forFeature won’t be able to find the connection initialized by Mongoose.forRoot.

You can declare “Mongoose.forRoot()” in your Test.createTestModule() statement or use a similar as me :

( please note that i have multiple database in my real project thats why i have a DatabaseModule )

Example

DatabaseModule

@Module({
    imports: [MongooseModule.forRoot('mongodb://localhost')],
})
export class DatabaseModule {}

CatModule

@Module({
    imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])], // note that database module is not imported here
    controllers: [CatController],
    components: [CatService, CatRepository],
})
export class CatModule {}

ApplicationModule ( for “normal” execution )

@Module({
  imports: [DatabaseModule, CatModule],
  controllers: [],
  components: [],
})
export class ApplicationModule {}

cat.e2e-spec.ts

    // ...
    beforeAll(async () => {
        const module = await Test.createTestingModule({
            imports: [DatabaseModule, CatModule],
        })
        .compile();
        // ....
    });

I think you’ve run into the same issue as me. In order to use jest for testing, nest uses ts-jest to compile test files. Somehow ts-jest doesn’t seem to handle synthetic default imports properly. As soon as I turned it off all tests passed.

I’ve created a separate tsconfig for jest:

// tsconfig.jest.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "allowSyntheticDefaultImports": false
  }
}

And configured ts-jest to use this in package.json:

// package.json
...
  "jest": {
    ...
    "globals": {
      "ts-jest": {
        "tsConfigFile": "./tsconfig.jest.json"
      }
    },
    ....
...

Don’t forget to also turn this off for e2e tests. By default, nest creates another configuration file for it under test/jest-e2e.json. I also added the globals object there:

...
"globals": {
      "ts-jest": {
        "tsConfigFile": "./tsconfig.jest.json"
      }
},
...

And voila, everything’s running. Hopefully that’ll work for the rest of us too.

How to Solve the Problem

@ebadia Hi. Please have a look at this. Give a token (name of the injected dependency) as provide (here, I use the default one: <entityName>Repository (e.g. "UserRepository"), and the class to use to instantiate this dependency in useClass (here, you want to inject an instance of “Repository” for your UsersService).

I spent a ton of time on finding a solution here so I am going to post what I came up with. Its a combination of @quezak solution and providing a mock value.

import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from "@nestjs/typeorm";
import * as request from 'supertest';
import { UsersModule } from './../src/modules/users/users.module';
import { UserEntity } from "./../src/modules/users/entities/user.entity";

describe('UserController (e2e)', () => {
  let app: INestApplication;

  const mockUser = new UserEntity({
    email: 'test@test.com'
  });

  beforeAll(async () => {
    
    const moduleFixture = await Test.createTestingModule({
      imports: [
        UsersModule
      ]
    })
      .overrideProvider(getRepositoryToken(UserEntity))
      .useValue({
        find: () => mockUser,
        findOne: () => mockUser,
        save: () => mockUser,
      })
      .compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

Hey guys trying to write some tests for mongoose models as well, having no luck with this, im getting TypeError: Object prototype may only be an Object or null: undefined

I’m having no luck at all with this, I still can’t work out how to mock the schema and even using the actual schema results in an error regardless of whether it’s included in components or imports

    const mod: TestingModule = await Test
      .createTestingModule({
        components: [
          RoleService
        ],
        controllers: [
          RolesController
        ],
        imports: [
          {
            provide: 'RoleSchemaModel',
            useValue: RoleSchema
          }
        ]
      })
      .compile();

results in TypeError: Object prototype may only be an Object or null: undefined while

    const mod: TestingModule = await Test
      .createTestingModule({
        components: [
          RoleService,
          {
            provide: 'RoleSchemaModel',
            useValue: RoleSchema
          }
        ],
        controllers: [
          RolesController
        ]
      })
      .compile();

results in Error: Nest can't resolve dependencies of the RoleService (?). Please verify whether [0] argument is available in the current context.

This is explained in the new version of the documentation: https://docs.nestjs.com/v5/

  1. Techniques -> Database (Testing section)
  2. Techniques -> Mongo (Testing section)

Hi all, I have tried by following these steps:

  1. Extending the Repository<Entity> class:
import {Repository} from "typeorm";
import {Label} from "../Models/Label";

export class LabelsRepository extends Repository<Label>{

}
  1. Declaring it on my dependency installer:
 const module = await Test.createTestingModule({

            controllers: [LabelsController],
            components: [{
                provide: 'LabelsRepository',
                useClass: LabelsRepository,
            }, LabelsService
            ]
        }).compile();
  1. Injecting it in my service constructor:
  private readonly labelRepository: LabelsRepository;

    constructor(@Inject('LabelsRepository')
                    labelRepository: LabelsRepository) {
        this.labelRepository = labelRepository;
    }

But I still have an error:

_Nest can't resolve dependencies of the LabelsService (?). Please verify whether [0] argument is available in the current context._

Am I wrong?

@mogusbi Not sure if you got @fwoelffel’s solution to work yet; but I had to put the mock in components instead of imports to get things working properly. Something like the following:

beforeEach(async () => {
    const mod: TestingModule = await Test
        .createTestingModule({
            components: [
                RoleService,
                {
                    provide: 'RoleSchemaModel',
                    useValue: {
                        // Your mock
                    }
                }
            ],
            controllers: [
                RoleController
            ],
        }
]
})
.compile();
    controller = mod.get<RoleController>(RoleController);
    service = mod.get<RoleService>(RoleService);
});

That is because the MongooseModule.forFeature(...) needs to inject your Connection object. You should either add MongooseModule.forRoot(...) to your imports or mock your RoleSchema.

EDIT: See @shane-melton’s response

I you want to mock the RoleSchema (which is the best option), try :

beforeEach(async () => {
  const mod: TestingModule = await Test
    .createTestingModule({
      components: [
        RoleService
      ],
      controllers: [
        RoleController
      ],
      imports: [
        {
          provide: 'RoleSchemaModel',
          useValue: {
            // Your mock
          }
        }
      ]
    })
    .compile();
  controller = mod.get<RoleController>(RoleController);
  service = mod.get<RoleService>(RoleService);
});

If you’re wondering why provide: 'RoleSchemaModel' have a look at this 😉

EDIT: Missing link
https://github.com/nestjs/mongoose/blob/master/lib/mongoose.utils.ts#L3

@mazyvan I ran into the same problem, here is what i found to work for me: My custom repository:

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  public async findOneByEmail(email: string): Promise<User> {
    return this.findOne({ where: { email } });
  }
}

My Service constructor

export class LoginService {
  constructor(
    @InjectRepository(UserRepository)
    private readonly userRepository: UserRepository,
  ) {}
// ...
}

and the spec file:

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        LoginService,
        { provide: getRepositoryToken(UserRepository), useValue: mockRepository },
      ],
    }).compile();
    service = module.get<LoginService>(LoginService);
  });

Notice that I get the repositoryToken for UserRepository instead of User

I’ll leave a note, since this issue comes up first in most google searches about mocks / unit tests with Typeorm in Nest.

TL;DR see the example below, or the corrected link from @jkchao above: the https://github.com/jkchao/blog-service/blob/nest/src/module/auth/__test__/auth.service.spec.ts

The recommended testing strategy (mentioned in the docs), to add { provides: getRepositoryToken(...), useWhatever } works nicely for simple modules. The problem is when your tested module imports any other module, that depends (maybe indirectly) on any other db providers. Then, you get an error, because the other modules imports TypeOrmModule.forFeature(...), which in turn “depends” on TypeOrmModule.forRoot:

    Nest can't resolve dependencies of the YourEntity2Repository (?). Please verify whether [0] argument is available in the current context.

You can, of course, recursively list all the required imported providers in your call to createTestingModule, and add all the mock db providers the same way, but it quickly becomes unmaintainable. A much simpler solution is to just import the whole tested module, and then use .overrideProvider for each DB entity that the module uses (directly or not):

        const testingModule = await Test.createTestingModule({ imports: [YourTestedModule] })
            .overrideProvider(getRepositoryToken(YourEntity1))
            .useValue(...)
            .overrideProvider(getRepositoryToken(YourEntity2))
            .useValue(...)
            .compile();

Maybe it’s worth mentioning this approach in the “Techniques > Database > Testing” docs? It took me a surprisingly long time looking for other workarounds (including mocking the db connection itself inside TypeOrmModule), until I found out about .overrideProvider.

The documentation is now moved to the root directory.

  1. TypeOrm -> https://docs.nestjs.com/techniques/database
  2. Mongo -> https://docs.nestjs.com/techniques/mongodb

Can someone help me to understand the issue? i’ve created an example of simple app with 2 modules (dummy using nest/mongoose and cats using custom provider as per the doc: https://github.com/ganchikov/nest_mongo_ut_sample

Both modules have tests:

  • dummy has the test as per @fwoelffel suggestion above
  • cats has the e2e test copied from the doc :

the app itself is successfully executed, but when I run the test I get the following error for both modules: `FAIL src\nest-mongoose\dummy.controller.spec.ts ● Test suite failed to run

TypeError: mongoose.Schema is not a constructor

  at Object.<anonymous> (nest-mongoose/dummy.schema.ts:2:43)
  at Object.<anonymous> (nest-mongoose/dummy.service.ts:24:14)
  at Object.<anonymous> (nest-mongoose/dummy.controller.spec.ts:11:14)
      at Generator.next (<anonymous>)
      at new Promise (<anonymous>)
  at handle (../node_modules/worker-farm/lib/child/index.js:44:8)
  at process.<anonymous> (../node_modules/worker-farm/lib/child/index.js:51:3)
  at emitTwo (../events.js:126:13)
  at process.emit (../events.js:214:7)
  at emit (../internal/child_process.js:772:12)
  at _combinedTickCallback (../internal/process/next_tick.js:141:11)
  at process._tickCallback (../internal/process/next_tick.js:180:9)

` and

`FAIL src\nest-provider\cats.controller.spec.ts ● Test suite failed to run

TypeError: mongoose.Schema is not a constructor

  at Object.<anonymous> (nest-provider/cat.schema.ts:2:39)
  at Object.<anonymous> (nest-provider/cats.providers.ts:1:252)
  at Object.<anonymous> (nest-provider/cats.module.ts:11:14)
  at Object.<anonymous> (nest-provider/cats.controller.spec.ts:13:13)
      at Generator.next (<anonymous>)
      at new Promise (<anonymous>)
  at handle (../node_modules/worker-farm/lib/child/index.js:44:8)
  at process.<anonymous> (../node_modules/worker-farm/lib/child/index.js:51:3)
  at emitTwo (../events.js:126:13)
  at process.emit (../events.js:214:7)
  at emit (../internal/child_process.js:772:12)
  at _combinedTickCallback (../internal/process/next_tick.js:141:11)
  at process._tickCallback (../internal/process/next_tick.js:180:9)

` I’ve played the whole day yesterday trying different options including suggestions from this thread: #438

The result is always the same 😦 What i am missing?

Is there any way at all to make this error message more helpful? Listing what has been included would be really good. I’ve been able to get to the bottom of it sometimes, but I’ve also just had to roll back my code and do something else. Also he was asking about database testing, can we get an example that doesn’t use mocks?

Hi @VinceOPS ,

It still not working, I have tried both, by extending Repository<Label> using LabelRepository class, and also by simply referring Repository.

Where am I doing wrong? Here you can find the repository: https://github.com/samueleresca/Blog.NestGettingStarted

@kunal-relan In this case something like the following should work:

UserService

@Component()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>
  ) {}
//...

The spec file:

import { getRepositoryToken } from '@nestjs/typeorm';
//...
 beforeEach( async () => {
    const module = await Test.createTestingModule({
      components: [ UsersService, {
        provide: getRepositoryToken(User),
        useClass: Repository
      } ],
      controllers: [ UsersController ],
    }).compile()
//...

I’m not sure about the useClass: Repository part, I would mock that out, but the provide part has to look like above

Does one has a idea to fix that? It can not be that there is no working sample to unit test a component or sample with this setup. I found multiple sample projects on GH but they all have no tests.

Hey!

I have almost the same situation as @samueleresca but with mongoose models. Neither registering a mock as component nor importing it resolves it.

Since almost every class to test somehow depends on a mongoose model I cannot write any tests. After reading through this issue I can’t provide anything new. I made sure to update all dependencies to the latest version (4.6.4 in case of nestjs).

So does anyone has a working example?

Hi @samueleresca

The default injection token (for TypeORM repositories) is <entityName>Repository. In your case, LabelRepository, not LabelsRepository.

@kiwikern I got this error with your code:

Type 'Mock<{ findOne: Mock<any, any[]>; }, []>' is not assignable to type '() => MockType<Repository<any>>'.
      Type '{ findOne: Mock<any, any[]>; }' is missing the following properties from type 'MockType<Repository<any>>': manager, metadata, createQueryBuilder, target, and 19 more

You can avoid typescript errors if you mention that your mock is partial:

export type MockType<T> = {
  [P in keyof T]?: jest.Mock<{}>;
};

@xavi-dev As it says, your mock definition is not complete. That’s why in the example there is // @ts-ignore telling the typescript compiler to ignore the missing properties error in your mock. Alternatively, you can either remove the type definition or define all 23 missing properties.

This issue was referenced in a question on Stackoverflow. Here is my solution: https://stackoverflow.com/a/55366343/4694994

I think for me, I had two questions.

  1. How do you test with a test DB

Solution: user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: Repository<User>,
    ) {}

    async findAll(): Promise<User[]> {
        return await this.userRepository.find();
    }
}

user.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';

describe('UserService', () => {
    let service: UserService;

    beforeAll(async () => {
        const module: TestingModule = await Test.createTestingModule({
            imports: [
                TypeOrmModule.forRoot(),
                TypeOrmModule.forFeature([User]),
            ],
            providers: [UserService],
        }).compile();
        service = module.get<UserService>(UserService);
    });
    it('should be defined', () => {
        expect(service).toBeDefined();
    });
});

  1. How do you test with a mocked repository

Solution:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: Repository<User>,
    ) {}

    async findAll(): Promise<User[]> {
        return await this.userRepository.find();
    }
}

user.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { UserRepository } from './user.repository';
import { UserService } from './user.service';

describe('UserService', () => {
    let service: UserService;

    beforeAll(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [
                UserService,
                {
                    provide: 'UserRepository',
                    useValue: UserRepository,
                },
            ],
        }).compile();
        service = module.get<UserService>(UserService);
    });
    it('should be defined', () => {
        expect(service).toBeDefined();
    });
});

user.repository.ts

import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UserRepository extends Repository<User> {}

@ganchikov @ChenShihao @AlbertoNitro I have the same problem. Any idea??

@xxxtonixxx It’s not clear how you would inject it, I’ve tried the following

  beforeEach(async () => {
    const mod: TestingModule = await Test
      .createTestingModule({
        components: [
          RoleService
        ],
        controllers: [
          RoleController
        ],
        imports: [
          MongooseModule.forFeature([
            {
              name: 'Role',
              schema: RoleSchema
            }
          ])
        ]
      })
      .compile();

    controller = mod.get<RoleController>(RoleController);
    service = mod.get<RoleService>(RoleService);
  });

but it only yields this error; Nest can't resolve dependencies of the useFactory (?). Please verify whether [0] argument is available in the current context.

I suppose it’s failing because in your RoleService you are injecting an mongoose model, which is not including in the testing module. You should create a mock or inject the real one