9
votes

Test des modèles de mangoustes avec NestJS

J'utilise le module mangouste de NestJS donc j'ai mon schéma et une interface, et dans mon service j'utilise @InjectModel pour injecter mon modèle. Je ne sais pas comment je peux me moquer du modèle à injecter dans mon service.

Mon service ressemble à ceci:

    TypeError: this.userModel is not a constructor

et dans mon test de service, j'ai ceci:

    const mockMongooseTokens = [
      {
        provide: getModelToken('User'),
        useValue: {},
      },
    ];

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
          providers: [
            ...mockMongooseTokens,
            AuthenticationService,
          ],
        }).compile();

        service = module.get<AuthenticationService>(AuthenticationService);
      });

Mais lorsque j'exécute le test, j'ai eu cette erreur:

    @Injectable()
    export class AuthenticationService {

        constructor(@InjectModel('User') private readonly userModel: Model<User>) {}

        async createUser(dto: CreateUserDto): Promise<User> {
            const model = new this.userModel(dto);
            model.activationToken = this.buildActivationToken();
            return await model.save();
          }
    }

Je voudrais aussi que mon modèle effectuer des tests unitaires dessus, comme indiqué dans ce article


1 commentaires

J'ai trouvé ici la solution de Kim Kern pour bien faire cela: stackoverflow.com/questions/55366037/...


3 Réponses :


15
votes

Votre question ne semble pas avoir suscité beaucoup d'attention, puisque j'ai rencontré le même problème, voici la solution que j'ai implémentée. J'espère que cela aidera d'autres passionnés de NestJS!

Comprendre le modèle mangouste

Le message d'erreur que vous obtenez est assez explicite: this.userModel n'est en effet pas un constructeur, car vous avez fourni un vide objet à useValue . Pour garantir une injection valide, useValue doit être une sous-classe de mongoose.Model . Le github repo mangouste donne lui-même une explication cohérente du concept sous-jacent (à partir de la ligne 63):

    const module = await Test.createTestingModule({
        providers: [
          AuthenticationService,
          {
            provide: getModelToken('User'),
            useValue: new mockUserModel(),
          },
        ],
      }).compile();

En d'autres termes, un modèle mangouste est une classe avec plusieurs méthodes qui tentent de se connecter à une base de données. Dans notre cas, la seule méthode Model utilisée est save () . Mongoose utilise la syntaxe de la fonction de constructeur javascript, la même syntaxe peut être utilisée pour écrire notre maquette.

TL; DR

La maquette doit être une fonction de constructeur, avec un save () param.

Ecriture de la maquette

Le test de service est le suivant:

  beforeEach(async () => {
    function mockUserModel(dto: any) {
      this.data = dto;
      this.save  = () => {
        return this.data;
      };
    }

    const module = await Test.createTestingModule({
        providers: [
          AuthenticationService,
          {
            provide: getModelToken('User'),
            useValue: mockUserModel,
          },
        ],
      }).compile();

    authenticationService = module.get<AuthenticationService>(AuthenticationService);
  });

J'ai aussi fait un peu de refactoring, pour tout envelopper dans le bloc beforeEach . L'implémentation save () que j'ai choisie pour mes tests est une simple fonction d'identité, mais vous pouvez l'implémenter différemment, en fonction de la manière dont vous souhaitez affirmer la valeur de retour de createUser () .

Limites de cette solution

Un problème avec cette solution est précisément que vous affirmez sur la valeur de retour de la fonction, mais ne pouvez pas affirmer sur le nombre d'appels, car save () n'est pas un jest.fn () . Je n'ai pas trouvé de moyen d'utiliser module.get pour accéder au modèle de jeton en dehors de la portée du module. Si quelqu'un trouve un moyen de le faire, merci de me le faire savoir.

Un autre problème est le fait que l'instance de userModel doit être créée dans la classe testée. Ceci est problématique lorsque vous voulez tester findById () par exemple, car le modèle n'est pas instancié mais la méthode est appelée sur la collection. La solution consiste à ajouter le mot clé new au niveau useValue :

 * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
 * class. You should not use the `mongoose.Model` class directly. The
 * [`mongoose.model()`](./api.html#mongoose_Mongoose-model) and
 * [`connection.model()`](./api.html#connection_Connection-model) functions
 * create subclasses of `mongoose.Model` as shown below.

Encore une chose ... h2>

La syntaxe return await ne doit pas être utilisée, car elle déclenche une erreur ts-lint (règle: no-return-await). Consultez le problème github doc .


1 commentaires

J'ai pu accéder à userModel en utilisant module.get comme ceci: userModel = await module.get (getModelToken ('User')); Ensuite, j'ai pu simuler la méthode de sauvegarde en tant que fonction jest.fn avec différentes valeurs résolues / rejetées.



4
votes

en réponse à la solution @jbh, un moyen de résoudre le problème de ne pas instancier la classe dans un appel d'une méthode comme findById () est d'utiliser des méthodes statiques, vous pouvez utiliser comme ça < pre> XXX

Plus d'informations sur les méthodes statiques: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static


0 commentaires

0
votes
add new before each:
describe('Create mockmodel', () => {


beforeEach(async () => {
    function mockUserModel(dto: any) {
      this.data = dto;
      this.save  = () => {
        return this.data;
      };
    }

    const module = await Test.createTestingModule({
        providers: [
          AuthenticationService,
          {
            provide: getModelToken('User'),
            useValue: mockUserModel,
          },
        ],
      }).compile();

    authenticationService = module.get<AuthenticationService>(AuthenticationService);
  });
})

1 commentaires

Bienvenue dans Stackoverflow. Veuillez décrire votre réponse pourquoi et comment résout-elle la question afin que les autres puissent comprendre votre réponse facilement.