NestJS Testing ConfigService works - testing

I am writing an application to handle requests and return predefined responses to allow testing of external REST endpoints by software that cannot have internal tests written into it currently. Therefore, my code uses the Nest JS framework to handle the routes, and then extracts values and returns data. The data returned is stored in external files.
To handle constant changes and different team usage, the program uses a .env file to give the base (root) directory where the files to respond are located. I am trying to write a test case to ensure that the NestJS ConfigService is working properly, but also to use as a base for all my other tests.
With different routes, different data files need to be returned. My code will need to mock all these files. As this data relies on the base ConfigService having read the .env to find the base paths, my routes are based on this starting point.
During development, I have a local .env file with these values set. However, I want to test without this .env file being used, so my tests do not rely on the presence of the .env file, since the CI/CD server, build server, etc., will not have a .env file.
I am simply trying a test file then to work with the configuration, to get set data from my mock.
nestjs-config.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigModule, ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
expandVariables: true,
}),
],
providers: [
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === 'FILES') {
return './fakedata/';
} else if (key === 'PORT') {
return '9999';
}
return null;
}),
},
},
],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});
The problem in this test is the default values are always returned, meaning the .env file was not read, but the provider had the code to handle this.
Ideally, I would like to create a fake class for testing so I could use it in all my test files. However, when trying to create the fake class, I get an error about other methods missing, that are unrelated to this class.
export class ConfigServiceFake {
get(key: string) {
switch (key) {
case 'FILES':
return './fakedata/';
case 'PORT':
return '9999';
}
}
}
This does not seem to execute, and it appears to still go through the original service.

I was able to adjust this and not need external references, including importing the configuration module, making the mock simpler, and not needing the full definitions.
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
afterEach(() => {
jest.clearAllMocks();
});
beforeEach(async () => {
const FakeConfigService = {
provide: ConfigService,
useValue: {
get: jest.fn((Key: string, DefaultValue: string) => {
switch (Key) {
case 'FILES':
return './fakedata/';
break;
case 'PORT':
return '9999';
break;
default:
return DefaultValue;
}
}),
},
};
const module: TestingModule = await Test.createTestingModule({
providers: [FakeConfigService],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});

Related

How to test dynamic modules in Nest.js?

My implementation is based on this article: https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370
I want to test my generic, Twilio-based SMS sender service that I share between multiple parts of my application. I want to configure it when I'm importing it from somewhere else, so I'm writing it as a dynamic module. On top of that, the options that I pass to the dynamic module are themselves constructed dynamically, they are read from my .env file. I'm using the factory pattern when I'm registering my provider:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
'.env',
],
validationSchema,
}),
SharedSmsModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService<EnvironmentVariables>) => {
return {
accountSid: configService.get('TWILIO_ACCOUNT_SID'),
authToken: configService.get('TWILIO_AUTH_TOKEN'),
smsSenderPhoneNumber: configService.get(
'TWILIO_SMS_SENDER_PHONE_NUMBER'
),
};
},
}),
],
})
export class AppModule {}
My shared-sms module calls the function provided in the registerAsync method in app.module.ts:
// shared-sms.module.ts
export interface SharedSmsModuleOptions {
accountSid: string;
authToken: string;
smsSenderPhoneNumber: string;
}
export interface SharedSmsModuleAsyncOptions extends ModuleMetadata {
imports: any[];
inject: any[];
useFactory?: (
...args: any[]
) => Promise<SharedSmsModuleOptions> | SharedSmsModuleOptions;
}
#Module({})
export class SharedSmsModule {
static registerAsync(
sharedSmsModuleAsyncOptions: SharedSmsModuleAsyncOptions
): DynamicModule {
return {
global: true,
module: SharedSmsModule,
imports: sharedSmsModuleAsyncOptions.imports,
providers: [
{
provide: 'SHARED_SMS_OPTIONS',
useFactory: sharedSmsModuleAsyncOptions.useFactory,
inject: sharedSmsModuleAsyncOptions.inject || [],
},
SharedSmsService,
],
exports: [SharedSmsService],
};
}
}
Now I have access to the options variables in my shared-sms.service:
// shared-sms.service
#Injectable()
export class SharedSmsService {
private twilioClient: Twilio;
constructor(
#Inject('SHARED_SMS_OPTIONS') private options: SharedSmsModuleOptions
) {
this.twilioClient = new Twilio(
this.options.accountSid,
this.options.authToken
);
}
async sendSms(sendSmsDto: SendSmsDto): Promise<MessageInstance> {
await validateOrReject(plainToInstance(SendSmsDto, sendSmsDto));
const smsData = {
from: this.options.smsSenderPhoneNumber,
to: sendSmsDto.to,
body: sendSmsDto.body,
};
return await this.twilioClient.messages.create(smsData);
}
}
So long everything seems to be working. But I'm having issues when I'm trying to test the service's sendSms function. I can write tests that work when I'm providing hardcoded Twilio test account values in my test file. But I don't want to commit them to the repository, so I would want to get them from my .env file. I have tried providing everything to the Test.createTestingModule function when I'm creating my moduleRef, based on what I did in the code that I already wrote, but I couldn't specify the Twilio test account values dynamically. As I don't see documentation regarding this issue, I feel like that I'm either missing a conceptual point (providing so many things in the test seems like an overkill) or there is a trivial work-around. Please help me figure out how to pass those values to my tests from my .env file

Nestjs pipe works when I manually create entity but not in jest test

I have a validation pipe to check input that works when I manually create a product(using postman), but it doesn't check when I run tests. any explanations?
my validator:
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private schema: ObjectSchema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
throw new HttpException('Validation failed', HttpStatus.BAD_REQUEST);
}
return value;
}
}
my controller:
#UsePipes(new JoiValidationPipe(productSchema))
#Post()
async create(#Body() createProductDto: CreateProductDto): Promise<Product> {
return (await this.productsService.create(createProductDto)).product;
}
my test:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ProductsController],
providers: [ProductsService],
}).compile();
controller = module.get<ProductsController>(ProductsController);
service = module.get<ProductsService>(ProductsService);
});
describe('create()', () => {
it('should fail to add a new product', async () => {
const result: Product = {
name: 'p',
price: -100,
category: 'junk',
};
expect(await controller.create(result)).toBe(result);
});
});
my schema:
export const productSchema: ObjectSchema = object({
createProductDto: object().keys({
name: string().min(5).required(),
price: number().integer().min(0).default(0),
category: string().min(5).required(),
}),
});
Pipes don't run unless you're going through the HTTP request. Same for other enhancers like guards and interceptors. If you want to test the pipe you can do that with supertest and e2e tests, or you can test the schema directly with joi in a different test suite

TypeError: Cannot read property 'cache' of undefined - VueJS

I created a Vue component which exports an async function. This component acts as a wrapper for calling my API. It's based on axios with a caching component that relies on localforage for some short lived persistence.
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
export default {
async cache() {
// Register the custom `memoryDriver` to `localforage`
await localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const store = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'tgi-cache'
})
// Create `axios` instance with pre-configured `axios-cache-adapter` using a `localforage` store
return setup({
// `axios` options
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
store // pass `localforage` store to `axios-cache-adapter`
}
})
}
}
Here is how I am importing and using this component in my views:
import api from '#/components/Api.vue'
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
api.cache().then(async (api) => {
const response = await api.get('/client/find?id='+userId)
thisIns.userData = response.data.data[0]
}).catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}
I can import this component and everything appears to work. I get data back from my API. However, immediately after every call, I get an error:
TypeError: Cannot read property 'cache' of undefined
Which references this line:
api.cache().then(async (api) => {
I am unable to understand why this is happening, or what it means. The error itself indicates that the component I am importing is undefined, though that's clearly not the case; if it were, the API call would ultimately fail I would suspect. Instead, I am lead to believe that perhaps I am not constructing/exporting my async cache() function properly.
Upon further review, I don't actually understand why the author has implemented it the way he has. Why would you want to create an instance of localForage every single time you make an API call?
I've opted not to use a component and to only instantiate an instance of localForage once.
main.js
import localforage from 'localforage'
import memoryDriver from 'localforage-memoryStorageDriver'
import { setup } from 'axios-cache-adapter'
// Register the custom `memoryDriver` to `localforage`
localforage.defineDriver(memoryDriver)
// Create `localforage` instance
const localforageStore = localforage.createInstance({
// List of drivers used
driver: [
localforage.INDEXEDDB,
localforage.LOCALSTORAGE,
memoryDriver._driver
],
// Prefix all storage keys to prevent conflicts
name: 'my-cache'
})
Vue.prototype.$http = setup({
baseURL: 'https://my.api',
cache: {
maxAge: 2 * 60 * 1000, // set cache time to 2 minutes
exclude: { query: false }, // cache requests with query parameters
localforageStore // pass `localforage` store to `axios-cache-adapter`
}
})
the view
export default {
data() {
return {
userId: this.$route.params.id,
userData: ''
}
},
methods: {
loadClient(userId) {
const thisIns = this;
thisIns.$http.get('/client/find?id='+userId)
.then(async (response) => {
thisIns.userData = response.data.data[0]
})
.catch(function (error) {
console.log(error)
})
},
},
created() {
this.loadClient(this.userId)
},
}

How override Provider in Angular 5 for only one test?

In one of my unit test files, I have to mock several times the same service with different mocks.
import { MyService } from '../services/myservice.service';
import { MockMyService1 } from '../mocks/mockmyservice1';
import { MockMyService2 } from '../mocks/mockmyservice2';
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useClass: MockMyService1 }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MapComponent);
mapComponent = fixture.componentInstance;
fixture.detectChanges();
});
describe('MyFirstTest', () => {
it('should test with my first mock', () => {
/**
* Test with my first mock
*/
});
});
describe('MySecondTest', () => {
// Here I would like to change { provide: MyService, useClass: MockMyService1 } to { provide: MyService, useClass: MockMyService2 }
it('should test with my second mock', () => {
/**
* Test with my second mock
*/
});
});
});
I see that the function overrideProvider exists, but I did not manage to use it in my test. When I use it in a "it", the provider doesn't change. I didn't manage to find an example where this function is called. Could you explain me how to use it properly? Or have you an other method to do that?
As of angular 6 I noticed that overrideProvider works with the useValue property. So in order to make it work try something like:
class MockRequestService1 {
...
}
class MockRequestService2 {
...
}
then write you TestBed like:
// example with injected service
TestBed.configureTestingModule({
// Provide the service-under-test
providers: [
SomeService, {
provide: SomeInjectedService, useValue: {}
}
]
});
And whenever you want to override the provider just use:
TestBed.overrideProvider(SomeInjectedService, {useValue: new MockRequestService1()});
// Inject both the service-to-test and its (spy) dependency
someService = TestBed.get(SomeService);
someInjectedService = TestBed.get(SomeInjectedService);
Either in a beforeEach() function or place it in an it() function.
If you need TestBed.overrideProvider() with different values for different test cases, TestBed is frozen after call of TestBed.compileComponents() as #Benjamin Caure already pointed out. I found out that it is also frozen after call of TestBed.get().
As a solution in your 'main' describe use:
let someService: SomeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: TOKEN, useValue: true}
]
});
// do NOT initialize someService with TestBed.get(someService) here
}
And in your specific test cases use
describe(`when TOKEN is true`, () => {
beforeEach(() => {
someService = TestBed.get(SomeService);
});
it(...)
});
describe(`when TOKEN is false`, () => {
beforeEach(() => {
TestBed.overrideProvider(TOKEN, {useValue: false});
someService = TestBed.get(SomeService);
});
it(...)
});
If the service is injected as public property, e.g.:
#Component(...)
class MyComponent {
constructor(public myService: MyService)
}
You can do something like:
it('...', () => {
component.myService = new MockMyService2(...); // Make sure to provide MockMyService2 dependencies in constructor, if it has any.
fixture.detectChanges();
// Your test here...
})
If injected service is stored in a private property, you can write it as (component as any).myServiceMockMyService2 = new MockMyService2(...); to bypass TS.
It's not pretty but it works.
As for TestBed.overrideProvider, I had no luck with that approach (which would be much nicer if it worked):
it('...', () =>{
TestBed.overrideProvider(MyService, { useClass: MockMyService2 });
TestBed.compileComponents();
fixture = TestBed.createComponent(ConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// This was still using the original service, not sure what is wrong here.
});
I was facing similar problem, but in a simpler scenario, just one test(describe(...)) with multiple specifications(it(...)).
The solution that worked for me was postponing the TestBed.compileComponents and the TestBed.createComponent(MyComponent) commands.
Now I execute those on each individual test/specification, after calling TestBed.overrideProvider(...) when needed.
describe('CategoriesListComponent', () => {
...
beforeEach(async(() => {
...//mocks
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])],
declarations: [CategoriesListComponent],
providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}]
});
}));
...
it('should call SetCategoryFilter when reload is false', () => {
const mockActivatedRouteOverride = {...}
TestBed.overrideProvider(ActivatedRoute, {useValue: mockActivatedRouteOverride });
TestBed.compileComponents();
fixture = TestBed.createComponent(CategoriesListComponent);
fixture.detectChanges();
expect(mockCategoryService.SetCategoryFilter).toHaveBeenCalledTimes(1);
});
Just for reference, if annynone meets this issue.
I tried to use
TestBed.overrideProvider(MockedService, {useValue: { foo: () => {} } });
it was not working, still the original service was injected in test (that with providedIn: root)
In test I used alias to import OtherService:
import { OtherService } from '#core/OtherService'`
while in the service itself I had import with relative path:
import { OtherService } from '../../../OtherService'
After correcting it so both test and service itself had same imports TestBed.overrideProvider() started to take effect.
Env: Angular 7 library - not application and jest
I needed to configure MatDialogConfig for two different test scenarios.
As others pointed out, calling compileCompents will not allow you to call overrideProviders. So my solution is to call compileComponents after calling overrideProviders:
let testConfig;
beforeEach(waitForAsync((): void => {
configuredTestingModule = TestBed.configureTestingModule({
declarations: [MyComponentUnderTest],
imports: [
MatDialogModule
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { testConfig } }
]
});
}));
const buildComponent = (): void => {
configuredTestingModule.compileComponents(); // <-- compileComponents here
fixture = TestBed.createComponent(MyComponentUnderTest);
component = fixture.componentInstance;
fixture.detectChanges();
};
describe('with default mat dialog config', (): void => {
it('sets the message property in the component to the default', (): void => {
buildComponent(); // <-- manually call buildComponent helper before each test, giving you more control of when it is called.
expect(compnent.message).toBe(defaultMessage);
});
});
describe('with custom config', (): void => {
const customMessage = 'Some custom message';
beforeEach((): void => {
testConfig = { customMessage };
TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: testConfig }); //< -- override here, before compiling
buildComponent();
});
it('sets the message property to the customMessage value within testConfig', (): void => {
expect(component.message).toBe(customMessage);
});
});

What is the best approach to test a HapiJS plugin, with Lab?

What is the best way to test a HapiJS plugin, for example one plugin that add routes and handlers.
Since I have to create an instance of Hapi.Server to run the plugins, should I define all the tests from the app's root, for all the plugins ?
or
should I manage to get THE instance of Hapi.Server in my plugin's local tests ?
If I go for the second option, my server will have registered all the plugins, including those that the plugin to be tested doesn't depends on.
What is the best way to approach this ?
Thanks in advance.
If you're using Glue (and I highly recommend it), you can create a manifest variable for each test (or group of tests) you want to execute. The manifest only needs to include plugins required for that test to execute properly.
And expose some sort of init function to actually start your server. Small example:
import Lab = require("lab");
import Code = require('code');
import Path = require('path');
import Server = require('../path/to/init/server');
export const lab = Lab.script();
const it = lab.it;
const describe = lab.describe;
const config = {...};
const internals = {
manifest: {
connections: [
{
host: 'localhost',
port: 0
}
],
registrations: [
{
plugin: {
register: '../http_routes',
options: config
}
},
{
plugin: {
register: '../business_plugin',
options: config
}
}
]
},
composeOptions: {
relativeTo: 'some_path'
}
};
describe('business plugin', function () {
it('should do some business', function (done) {
Server.init(internals.manifest, internals.composeOptions, function (err, server) {
// run your tests here
});
});
});
init function:
export const init = function (manifest: any, composeOptions: any, next: (err?: any, server?: Hapi.Server) => void) {
Glue.compose(manifest, composeOptions, function (err: any, server: Hapi.Server) {
if (err) {
return next(err);
}
server.start(function (err: any) {
return next(err, server);
});
});
};