5

I want to e2e an endpoint called /users with nestjs but I got an error. I have doubts how to make the test pass with a guard.

First error

Nest can't resolve dependencies of the UserModel (?). Please make sure that the argument DatabaseConnection at index [0] is available in the MongooseModule context.

Second error

expected 200 "OK", got 401 "Unauthorized"

App.module

@Module({
  imports: [
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.mongoUri,
        useNewUrlParser: true,
      }),
      inject: [ConfigService],
    }),
    GlobalModule,
    UsersModule,
    AuthModule,
    PetsModule,
    RestaurantsModule,
    ConfigModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(TokenDataMiddleware)
      .forRoutes({ path: '*', method: RequestMethod.ALL });
  }
}

UsersService

@Injectable()
export class UsersService {
  constructor(
    @InjectModel('User') private readonly userModel: Model<UserDocument>,
    private readonly utilsService: UtilsService,
    private readonly configService: ConfigService,
  ) { }
async getAllUsers(): Promise<UserDocument[]> {
    const users = this.userModel.find().lean().exec();
    return users;
  }
}

Controller

@Controller('users')
export class UsersController {
    constructor(private readonly usersService: UsersService, private readonly utilsService: UtilsService) { }
    @Get()
    @ApiBearerAuth()
    @UseGuards(JwtAuthGuard)
    async users() {
        const users = await this.usersService.getAllUsers();
        return users;
    }

e2e file

describe('UsersController (e2e)', () => {
  let app: INestApplication;
  beforeAll(async () => {
    const testAppModule: TestingModule = await Test.createTestingModule({
      imports: [AppModule, GlobalModule,
        UsersModule,
        AuthModule,
        PetsModule,
        RestaurantsModule,
        ConfigModule],
      providers: [],
    }).compile();

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

  it('GET all users from API', async () => {
    // just mocked users;
    const users = getAllUsersMock.buildList(2);
    const response = await request(app.getHttpServer())
      .get('/users')
      .expect(200);
  });

  afterAll(async () => {
    await app.close();
  });
});

anthony willis muñoz
  • 2,346
  • 3
  • 16
  • 33

1 Answers1

12

In a unit test, you test a single unit (service, controller,...) meaning you import one unit and mock all its dependencies. In an e2e test, however, you want to test your whole application so you should import the root module (AppModule) instead of single units or modules. Sometimes you might want to mock particular parts of your application like a database or a 3rd-party API; you can do that with overrideProvider etc.

In your case, you are probably missing the forRoot import of the MongooseModule from your AppModule. Instead of restructuring parts of your application, import the AppModule:

await Test.createTestingModule({
      imports: [AppModule],
    }).compile()
      .overrideProvider(HttpService)
      .useValue(httpServiceMock);

You need to authenticate against your API if it's protected by a guard. You can either create a JWT programatically or use your API for it. I'm assuming you have an endpoint for authentication in the following example:

const loginResponse = await request(app.getHttpServer())
  .post('/auth/login')
  .send({ username: 'user', password: '123456' })
  .expect(201);
// store the jwt token for the next request
const { jwt } = loginResponse.body;

await request(app.getHttpServer())
  .get('/users')
  // use the jwt to authenticate your request
  .set('Authorization', 'Bearer ' + jwt)
  .expect(200)
  .expect(res => expect(res.body.users[0])
    .toMatchObject({ username: 'user' }));
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • Hi thanks for you answer, I update the question with your changes(it works) but keep my question about mock jwt token for pass JwtAuthGuard. thanks ! – anthony willis muñoz Oct 13 '19 at 16:20
  • @anthonywillismuñoz See my edit. The 401 error is as expected since your API is protected. It's actually good to test that unauthenticated requests cannot access your API. For testing the protected resource, you have to set the Authorization header, see the example. Also, you do not need to import all your modules. *Only* import the AppModule. It itself will import all other modules. – Kim Kern Oct 13 '19 at 16:29
  • @Kim Kern What is httpServiceMock? I faced the same problem and cannot understand it – MegaRoks Nov 13 '19 at 09:01
  • @MegaRoks Have a look at this thread on how to create a mock: https://stackoverflow.com/a/55366343/4694994 – Kim Kern Nov 13 '19 at 10:39