Unable to mock elasticsearch service in nestjs - javascript

Current behavior
Nest can't resolve dependencies of the ElasticsearchService (?). Please make sure that the argument at index [0] is available in the ElasticsearchModule context.
Expected behavior
Create ElasticSearchService in test module
Minimal reproduction of the problem with instructions
import { Test, TestingModule } from '#nestjs/testing';
import { RepliesController } from './replies.controller';
import { ElasticsearchService, ElasticsearchModule } from '#nestjs/elasticsearch';
import { Client } from 'elasticsearch';
describe('Replies Controller', () => {
let module: TestingModule;
let elasticsearch: ElasticsearchService;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController],
components: [ElasticsearchService, {provide: Client, useValue: {}}],
}).compile();
elasticsearch = module.get<ElasticsearchService>(ElasticsearchService);
});
it('should be defined', () => {
const controller: RepliesController = module.get<RepliesController>(RepliesController);
expect(controller).toBeDefined();
});
});
Environment
[Nest Information]
elasticsearch version : 0.1.2
common version : 5.4.0
core version : 5.4.0

You need to override the providers using methods exposed by the test module instead of doing it in the components array:
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController]
})
.overrideProvider(ElasticsearchService)
.useValue(/* your mock or whatever */)
.compile();
There are examples of this in the NestJs docs in the testing section.

Related

How to use redis client in nestjs?

I have a microservice in NestJs, here is the main.ts file:
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
}
})
await app.listen();
console.log('Redis is running');
}
bootstrap();
I want to make calls to redis db from the service:
import { createClient } from '#redis/client';
async createWorkspaceShareCode(data: GetWorkspaceShareCodeMessage) {
const client = createClient({
url: 'redis://localhost:6379'
});
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
return value;
}
But that's not what I want, here are the issues:
every time I make request, it creates new connection
I need to write this code in each function to connect to redis (even though I'm already connected in main.ts file)
Here is the question: how can I use some kind of RedisService in my NestJs microservice?
I have already tried installing redis and nestjs-redis libraries, but the first one creates the issue described above and the second one just throws me an error: https://github.com/skunight/nestjs-redis/issues/97
What I would do is keep your createMicroservice as you have it as it'll be encessary for a Redis based microservice server. Then you can create a module that exports a redis service like so:
#Module({
providers: [
{
provide: 'REDIS_OPTIONS',
useValue: {
url: 'redis://localhost:6379'
}
},
{
inject: ['REDIS_OPTIONS']
provide: 'REDIS_CLIENT',
useFactory: async (options: { url: string }) => {
const client = createClient(options);
await client.connect();
return client;
}
}
],
exports: ['REDIS_CLIENT'],
})
export class RedisModule {}
Now in the module that contains the service you want to use add RedisModule to the imports array and in the service you can do #Inject('REDIS_CLIENT') private readonly redis: Redis to inject the redis instance and use it with this.redis.get('key') and this.redis.set('key', value)

Error: Need to call TestBed.initTestEnvironment() first

I'm trying do a test in angular of a service.
This is my part of the code
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService]
});
injector = getTestBed();
service = injector.inject(AddressService);
httpTestingController = injector.inject(HttpTestingController);
// service = TestBed.inject(AddressService);
});
afterEach(() => {
httpTestingController.verify();
})
httpTestingController = TestBed.inject(HttpTestingController);
it('should be created', () => {
expect(service).toBeTruthy();
});
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
it('getAddress() should return data', () => {
service.getAddress().subscribe((res) => {
expect(res).toEqual(dummyAddressListResponse);
});
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
})
});
At the moment of run the test ng test --main src/app/services/address/address.service.spec.ts
I'm seeing this error Error: Need to call TestBed.initTestEnvironment() first
I have searched and don't see any solution, Has it happened to someone?
For jest users - just add the following code in setup-jest.js.
Because jest needs to be initialized.
import { TestBed } from "#angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "#angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
the first thing: --main shouldn't be used, it points to an entrypoint, not to a desired test, and should be src/test.ts.
To run a single test use the next command:
ng test --include "app/services/address/address.service.spec.ts"
The test should be a bit different:
describe('AddressService', () => {
let service: AddressService;
let injector: TestBed;
let httpTestingController: HttpTestingController;
beforeEach(async () => {
// let's compile TestBed first.
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AddressService],
}).compileComponents();
// let's use TestBed.injector.
service = TestBed.inject(AddressService);
httpTestingController = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpTestingController.verify();
})
it('should be created', () => {
expect(service).toBeTruthy();
});
it('getAddress() should return data', () => {
const dummyAddressListResponse = {
data: [
{direccion: 'address1'}, {Colas: 'queue1'},
{direccion: 'address2'}, {Colas: 'queue2'}
],
};
let actual: any;
service.getAddress().subscribe((res) => actual = res);
const req = httpTestingController.expectOne(`${environment.URI}/mock-address`);
expect(req.request.method).toBe('GET');
req.flush(dummyAddressListResponse);
expect(actual).toEqual(dummyAddressListResponse);
});
});
Is this using ng-packagr (i.e. an angular library)? If so you might want to check that there are no node_modules under the ./project/ folder.
This was throwing me this exact same error. The moment I deleted the node_modules under the project folder it all started to work again.
Source: https://github.com/ngneat/spectator/issues/546
The problem is that it must run everything from test.ts.
So instead of run ng test --main src/app/services/address/address.service.spec.ts command, just change const context = require.context('./', true, /\.spec\.ts$/); to const context = require.context('./', true, /address\.service\.spec\.ts$/); and use ng test command.

How to use service/module inside of another module?

In the main.ts file of my nestJS application I would like to add some fixture data to my database if the app is starting in dev mode:
const app = await NestFactory.create(AppModule)
await app.listen(port, async () => {
if (!production) {
const User = this.db.collection('user')
await User.deleteMany({})
await User.insertMany(user)
}
})
Of course this is not working, as I do not have db at this time.
I'm defining the database connection in a module and this is how my database.module.ts looks like.
Is it possible to put the fixtures parts (drop database and add fixture data) in the database.module? The reason why I think I have to add it to the main.ts is that I need to run it on application start, not at every db connection.
import { Module, Inject } from '#nestjs/common'
import { MongoClient, Db } from 'mongodb'
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT'],
useFactory: async (dbClient): Promise<Db> => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017')
dbClient.client = client
const db = client.db('database')
return db
} catch (error) {
console.error(error)
throw error
}
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT']
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
async onModuleDestroy() {
await this.dbClient.client.close()
}
}
With that I can use my db in every other module, but that doesn't help me to get db connection at start up of the application:
import { Module } from '#nestjs/common'
import { MyService } from './my.service'
import { MyResolvers } from './my.resolvers'
import { DatabaseModule } from '../database.module'
#Module({
imports: [DatabaseModule],
providers: [MyService, MyResolvers]
})
export class MyModule {}
If you're looking to get the database client in your main.ts you need to get into the client from the container system nest has. You can do that with the following line, used before your app.listen()
const db = app.get('DATABASE_CLIENT', { strict: false })
And then instead of this.db you'll just use db.client.collection()

NestJS: Supertest e2e tests skip serializer interceptors

I'm testing an AuthenticationController using supertest. To do so, I am mocking my application using the same configuration than the one I use in my main file main.ts:
// authentication.controller.ts
describe("The AuthenticationController", () => {
let app: INestApplication;
beforeEach(async () => {
userData = {
...mockedUser,
};
const userRepository = {
create: jest.fn().mockResolvedValue(userData),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
const module = await Test.createTestingModule({
controllers: [...],
providers: [...],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
await app.init();
});
});
This mostly works, but whenever I am testing a controller that should not return a password or an id for example - because of the #Exclude() decorator in an entity definition - the test still returns it to me.
Testing the endpoint manually on Postman still works well.
Does anyone know what could cause that issue?
I just got an answer from one of the developers of NestJS on their official Discord: https://discord.com/invite/nestjs
It turns out the error came from the fact that when mocking the return value of create in my userRepository, I was actually returning an object instead of an instance of a class. Therefore, the following lines had to be replaced:
const userRepository = {
create: jest.fn().mockResolvedValue(userData),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
By the following:
const userRepository = {
create: jest.fn().mockResolvedValue(new User(userData)),
save: jest.fn().mockReturnValue(Promise.resolve()),
};
By simply returning an object, the decorators are not taken into account, so a class instance must be returned.

Injecting Mocks in NestJS Application for Contract Testing

Issue
I'm looking for a way to bring up a NestJS application with mocked providers. This is necessary for provider contract tests because a service needs to be brought up in isolation. Using the Pact library, testing the provider assumes that the provider service is already running. It needs to be able to make HTTP requests against the actual server (with some dependencies mocked if necessary). PactJS
Current Research
I've looked into the docs for NestJS and the closest solution I can find is pasted below. From what I can tell, this solution tells the module to replace any provider called CatsService with catsService. This theoretically would work for provider contract testing purposes, but I don't think this allows for the entire app to be brought up, just a module. There is no mention in the docs for being able to bring up the app on a specific port using the testing module. I've tried to call app.listen on the returned app object and it fails to hit a breakpoint placed right after the call.
import * as request from "supertest";
import { Test } from "#nestjs/testing";
import { CatsModule } from "../../src/cats/cats.module";
import { CatsService } from "../../src/cats/cats.service";
import { INestApplication } from "#nestjs/common";
describe("Cats", () => {
let app: INestApplication;
let catsService = { findAll: () => ["test"] };
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [CatsModule]
})
.overrideProvider(CatsService)
.useValue(catsService)
.compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET cats`, () => {
return request(app.getHttpServer())
.get("/cats")
.expect(200)
.expect({
data: catsService.findAll()
});
});
afterAll(async () => {
await app.close();
});
});
Java Example
Using Spring a configuration class, mocks can be injected into the app when running with the "contract-test" profile.
#Profile({"contract-test"})
#Configuration
public class ContractTestConfig {
#Bean
#Primary
public SomeRepository getSomeRepository() {
return mock(SomeRepository.class);
}
#Bean
#Primary
public SomeService getSomeService() {
return mock(SomeService.class);
}
}
Update
Since version 4.4 you can also use listen since it now also returns a Promise.
You have to use the method listenAsync instead of listen so that you can use it with await:
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(AppService).useValue({ root: () => 'Hello Test!' })
.compile();
app = moduleFixture.createNestApplication();
await app.init();
await app.listenAsync(3000);
^^^^^^^^^^^^^^^^^^^^^
});
Then you can make actual http requests instead of relying on supertest. (I am using the nodejs standard http library in this example.)
import * as http from 'http';
// ...
it('/GET /', done => {
http.get('http://localhost:3000/root', res => {
let data = '';
res.on('data', chunk => data = data + chunk);
res.on('end', () => {
expect(data).toEqual('Hello Test!');
expect(res.statusCode).toBe(200);
done();
});
});
});
Don't forget to close the application or otherwise your test will run until closed manually.
afterAll(() => app.close());

Categories

Resources