Nest: How to test a controller with a service that has @Inject in its constructor?

Created on 20 Oct 2017  路  33Comments  路  Source: nestjs/nest

I have a question regarding testing as described on https://docs.nestjs.com/advanced/unit-testing:

beforeEach(async () => {
    const module = await Test.createTestingModule({
        controllers: [CatsController],
        components: [CatsService],
      }).compile();

    catsService = module.get<CatsService>(CatsService);
    catsController = module.get<CatsController>(CatsController);
});

The CatService is quite simple. How about this?

constructor(@Inject(CAT_MODEL_TOKEN) private readonly catModel: Model<Cat>) {}

How are we supposed to provide the catModel to CatsService in beforeEach? Without providing any parameters I get an error:

Nest can't resolve dependencies of the CatsService. Please verify whether all of them are available in the current context.

type question 馃檶

Most helpful comment

@adrien2p finally 馃拑 problem solved, thank you so much.

All 33 comments

Hi !

If you can inject something in your service, that supposed than you have a provider which is in the components list of your module and then it's need to appear in module configuration in your test.

something like this :

beforeEach(async () => {
    const module = await Test.createTestingModule({
        controllers: [CatsController],
        components: [
            CatsService,
            {
                provide: 'CAT_MODEL_TOKEN',
                useValue: Cat
            }
        ],
      }).compile();

    catsService = module.get<CatsService>(CatsService);
    catsController = module.get<CatsController>(CatsController);
});

and you need to inject it in your service like this :
constructor(@Inject(CAT_MODEL_TOKEN) private readonly catModel: typeof Model) {}
instedOf
constructor(@Inject(CAT_MODEL_TOKEN) private readonly catModel: Model<Cat>) {}

@adrien2p thanks, however I still get the same error.

Could you past your service and your test (the top of it)

@adrien2p
Service:

@Component()
export class MessageService {
    constructor(@Inject(MESSAGE_MODEL_TOKEN) private readonly messageModel: typeof Model) {}
   // ...
}

Test:


describe('MessageController', () => {
    let messageController: MessageController;
    let messageService: MessageService;

    beforeEach(async () => {

        const module = await Test.createTestingModule({
            controllers: [MessageController],
            components: [MessageService, {
                provide: MESSAGE_MODEL_TOKEN,
                useValue: Message
            }],
        }).compile();

        messageService = module.get<MessageService>(MessageService);
        messageController = module.get<MessageController>(MessageController);

    });
   // ...
});

Module:

@Module({
    modules: [DatabaseModule],
    controllers: [MessageController],
    components: [
        MessageService,
        ...messageProviders
    ],
})
export class MessageModule {}

Maybe a weard question but your MESSAGE_MODEL_TOKEN is between quotes ?

Hi @ArtworkAD,
Hmm.. The code looks fine, we need more context here. 馃檨 Is MESSAGE_MODEL_TOKEN the only required dependency injected by the MessageService?

@kamilmysliwiec yes it is

originally the message service looks like

@Component()
export class MessageService {
    constructor(@Inject(MESSAGE_MODEL_TOKEN) private readonly messageModel: Model<Message>) {}
    // ...
}

And using this service in tests is not working:

beforeEach(async () => {
        const module = await Test.createTestingModule({
            controllers: [MessageController],
            components: [MessageService],
        }).compile();

        messageService = module.get<MessageService>(MessageService);
        messageController = module.get<MessageController>(MessageController);
});
 Nest can't resolve dependencies of the MessageService. Please verify whether all of them are available in the current context.

      at Injector.<anonymous> (node_modules/@nestjs/core/injector/injector.js:156:23)
          at Generator.next (<anonymous>)
      at fulfilled (node_modules/@nestjs/core/injector/injector.js:4:58)
      at process._tickCallback (internal/process/next_tick.js:109:7)

@ArtworkAD because there's no MESSAGE_MODEL_TOKEN provider. Did you try @adrien2p's solution?

Hi @ArtworkAD,
Any updates here 馃檪 ?

@kamilmysliwiec @adrien2p trying the solution I ran into another issue: Cat is an interface

export interface Cat extends Document {
    //...
}

I get

Cat only refers to a type, but is being used as a value here

here

components: [
    CatsService,
    {
        provide: 'CAT_MODEL_TOKEN',
        useValue: Cat
     }
]

Any ideas?

Hey @ArtworkAD
your Cat should not be an interface but must be a class which implement an interface.
For exemple to provide my User entity i do the following code:
export class User extends Model<User>

And then :

{
    provide: 'UsersRepository',
    useValue: User,
};

In fact you can't use an interface as a value.

@adrien2p but per https://docs.nestjs.com/recipes/mongodb the model is an interface

export interface Cat extends Document {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

@ArtworkAD
I think you need to make a Class for Cat
Then make it extends from that Interface CatInterface

export class Cat extends Model<CatInterface> {}

@shekohex I get

no base constructor has the specified number of type arguments

Could you past your code ?

@adrien2p the code is exactly as in https://github.com/nestjs/nest/issues/194#issuecomment-338262944 with Cat being an Interface as described in the documentation https://docs.nestjs.com/recipes/mongodb

When I try your suggestion export class User extends Model<User>

import { Model } from "mongoose";
import { Cat } from "./interface/cat.interface";

export class CatModel extends Model<Cat> {}

I get a error

no base constructor has the specified number of type arguments

The example provide by @shekohex was just an example there is no constructor in your class ... it鈥檚 normal ?

For mongoose dis you try something like

export const catsProviders = [
  {
    provide: 'CatModelToken',
    useFactory: (connection: Connection) => connection.model('Cat', CatSchema),
    inject: ['DbConnectionToken'],
  },
];

@adrien2p Yes I have this. I am not sure what constructor to provide. A basic constructor like

export class CatModel extends Model<Cat> {
    constructor() {
        super();
    }
}

does not solve the problem. I still get no base constructor has the specified number of type arguments

Did you try my last proposal ?

@adrien2p yes, I am stuck with creating the CatModel, since I am not sure what constructor to provide. Besides that, introducing a CatModel only for testing seems wrong to me...

Please remove the model and try my last proposal

@adrien2p do you mean this:

export const catsProviders = [
  {
    provide: 'CatModelToken',
    useFactory: (connection: Connection) => connection.model('Cat', CatSchema),
    inject: ['DbConnectionToken'],
  },
];

I have this in my code. How does this effect testing?

Because you can inject it without create a class for you model

@adrien2p

So this is the service that needs the catModel:

@Component()
export class CatService {
    constructor(@Inject(CAT_MODEL_TOKEN) private readonly catModel: Model<Cat>) {}
    // ...
}

and the test is giving me an error

const module = await Test.createTestingModule({
        controllers: [CatController],
        components: [
            CatService
        ],
}).compile();

because dependencies of CatService are not resolved. And I have this

export const catsProviders = [
  {
    provide: 'CatModelToken',
    useFactory: (connection: Connection) => connection.model('Cat', CatSchema),
    inject: ['DbConnectionToken'],
  },
];

correctly setup. How can

you can inject it without create a class for you model

solve this problem?

So now in your components array you can add after the catService like this
components: [catService, ...catsProviders]

@adrien2p finally 馃拑 problem solved, thank you so much.

Not sure if it's still relevant after a year, but when you are unit testing your controller, you don't want to worry about it's dependencies so you mock them out by doing:

class MockCatsService {
}
describe('CatsController', () => {

    beforeEach(async () => {
        catsController: CatsController;
        catsService: CatsService;

        const module = await Test.createTestingModule({
            controllers: [CatsController],
            providers: [
                { provide: CatsService, useClass: MockCatsService },
            ],
        }).compile();

        catsService = module.get<CatsService>(CatsService);
        catsController = module.get<CatsController>(CatsController);
    });

});

so when your CatsService changes you don't have to update it in every single place you use it.

@adrien2p finally 馃拑 problem solved, thank you so much.

Could you share how you fixed this. I am still getting issues while unit testing. I am getting "Nest can't resolve dependencies of my service". Below are my code:

export class DeviceService extends BaseService {
    constructor(@InjectModel('devices') private readonly _deviceModel: Model<Device>) {
        super();
    }

    async getDevices(group): Promise<any> {
        try {
            return await this._deviceModel.find({ Group: group }).exec();
        } catch (error) {
            return Promise.reject(error);
        }
    }
}
@Controller()
export class DeviceController {
    constructor(private readonly deviceService: DeviceService) {
    }

   @Get(':group')
   async getDevices(@Res() response, @Param('group') group): Promise<any> {
        try {
            const result = await this.deviceService.getDevices(group);
            return response.send(result);
        }
        catch (err) {
            return response.status(422).send(err);
        }
    }
}
describe('DeviceController', () => {
    let deviceController: DeviceController;
    let deviceService: DeviceService;

    const response = {
        send: (body?: any) => { },
        status: (code: number) => response,
    };

    beforeEach(async () => {
        const module = await Test.createTestingModule({
            controllers: [DeviceController],
            providers: [DeviceService, ...deviceProviders],
        }).compile();

        deviceService = module.get<DeviceService>(DeviceService);
        deviceController = module.get<DeviceController>(DeviceController);
    });

    describe('getDevices()', () => {
        it('should return an array of devices', async () => {
            const result = [{
                Group: 'group_abc',
                DeviceId: 'device_abc',
            },
            {
                Group: 'group_xyz',
                DeviceId: 'device_xyz',
            }];
            jest.spyOn(deviceService, 'getDevices').mockImplementation(() => result);

            expect(await deviceController.getDevices(response, null)).toBe(result);
        });
    });
});

When I am running my code above, I am getting: "Nest can't resolve dependencies of the DeviceService (?). Please make sure that the argument at index [0] is available in the current context."

@univerze
Somewhat correct: actually you'd mock the entire file to get rid of the provider import in every test that uses the service.

jest.mock('/path/to/service', MockService);

MockService

@univerze
Somewhat correct: actually you'd mock the entire file to get rid of the provider import in every test that uses the service.

```ts
jest.mock('/path/to/service', MockService);

Hi I don't want to mock services as I mentioned above for maximum coverage I only wanted to mock the external dependencies like mongoose connection.

I have this problem:
Nest can't resolve dependencies of the CardService (QuiCardService, ?, RequestCardRepository, CardRepository, AccountRepository, CustomerRepository). Please make sure that the argument at index [1] is available in the CardModule context.

When i trying run test.
My code:

describe("Card api", () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [CardModule],
components: [
CardService
]
})
.compile();

app = module.createNestApplication();
await app.init();

});

CardModule:
@Module({
imports: [TypeOrmModule.forFeature([Card, Customer, Account, RequestCard])],
providers: [CardService, QuiCardService],
exports: [CardService, QuiCardService],
controllers: [CardController],
})
export class CardModule { }

CardService:
@Injectable()
export class CardService {
private voltaQuiApi: AxiosInstance;
constructor(
@Inject(QuiCardService)
private readonly quiCardService: QuiCardService,
@InjectConnection()
private readonly connection: Connection,
@InjectRepository(RequestCard)
private readonly reqCardRepository: Repository,
@InjectRepository(Card)
private readonly cardRepository: Repository,
@InjectRepository(Account)
private readonly accountRepository: Repository,
@InjectRepository(Customer)
private readonly customerRepository: Repository,
) {
this.voltaQuiApi = axios.create({
baseURL: process.env.VOLTAQUI_URL,
auth: {
username: process.env.VOLTAQUI_USERNAME,
password: process.env.VOLTAQUI_PASSWORD
}
});
}

Can someone help me?

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings