I try to unittest my NestJs Controller class. I already mocked my Service using ts-mockito but now I struggle to create the mongoose Objects I want to return and expect to get returned by the controller. How do I manage to create new Model Object to test with?
this is my Service:
#Injectable()
export class ProjectService {
constructor(
#InjectModel('Project') private readonly projectModel: Model<Project>,
private tagService: TagService,
) {} ...
This is my Model
let schema = new Schema({
name: {type: String, required: true},
description: String,
created: {type: Date, default: Date.now},
});
export const ProjectSchema = schema;
export interface Project extends Document {
readonly name: string,
readonly description: string,
readonly created: Date,
}
And this is my Module:
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Project', schema: ProjectSchema }]),
],
controllers: [
ProjectController
],
providers: [
ProjectService,
],
})
export class ProjectModule {}
This is my Test:
describe('ProjectController', async () => {
let projectController: ProjectController;
let projectServiceMock: ProjectService = mock(ProjectService);
let projectModel: Model<Project>;
beforeAll(async () => {
projectModel = mock(Model);
const module: TestingModule = await Test.createTestingModule({
controllers: [ProjectController],
providers: [
{
provide: ProjectService,
useValue: instance(projectServiceMock)
},
{
provide: 'Project',
useValue: instance(projectModel)
}
]
}).compile();
projectController = module.get<ProjectController>(ProjectController);
});
Now I am trying to create a new Object of Project and return it from my service and expect it from the controller:
it('should return Project with id from projectService', async () => {
const project = new projectModel({name: 'ProjectName', description: 'ProjectDescription'});
let result = Promise.resolve(project);
when(projectServiceMock.getById('projectId')).thenReturn(result);
await expect(projectController.getById('projectId')).toEqual(result);
});
But I get this error:
Nest cannot find given element (it does not exist in current context)
25 |
26 | projectController = module.get<ProjectController>(ProjectController);
> 27 | projectModel = module.get<Model<Project>>('Project');
| ^
28 | });
29 |
30 | describe('getAll', async () => {
As I think I can read from the error message there must be something wrong with 'getting' the model to the test but I really don't know how I can get the Model without initiating a connection or so...
What can I do? Do you have some example code that worked for you?
Use getModelToken function exposed in #nestjs/mongoose:
import {getModelToken} from '#nestjs/mongoose';
const module = await Test.createTestingModule({
providers: [
{
provide: getModelToken('ModelName'),
useValue: ModelMock,
},
],
}).compile()
modelMock = module.get<mongoose.Model<any>>('ModelNameModel'); // The getModelFunction just appends 'Model' to the Model name
Related
I'm Using the in memory mongoose database for create my Unit test, and I want to create a document before the tests.
My interface is:
export interface IUsers {
readonly name: string;
readonly username: string;
readonly email: string;
readonly password: string;
}
and my beforeEach is:
import { MongooseModule } from "#nestjs/mongoose";
import { Test, TestingModule } from "#nestjs/testing";
import { closeInMongodConnection, rootMongooseTestModule } from '../test-utils/mongo/MongooseTestModule';
import { User, UserSchema } from "./schemas/users.schema";
import { UsersService } from "./users.service";
describe("UsersService", () => {
let service: UsersService;
let testingModule: TestingModule;
let userModel: Model<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule(),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
//create user
userModel = testingModule.get<Model<User>>(
'UserModel',
);
});
I get an error TypeError: Cannot read pro perties of undefined (reading 'get') during the test. I tried to use let userModel: Model<IUsers>; But I get the same error.
Use either testingModule or module.
You declared testingModule but never initialized.
let testingModule: TestingModule; This part is undefined unless something is assigned to it.
Try like this
describe('UsersService', () => {
let testingModule: TestingModule;
let userModel: Model<User>;
let userService: UserService;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
imports: [
rootMongooseTestModule,
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
providers: [UsersService],
}).compile();
userService = testingModule.get<UsersService>(UsersService);
userModel = testingModule.get<Model<User>>('UserModel');
// await userModel.create(...) or whatever methods you have
});
});
Is it possible to add a serial port and USB package in NestJS? I can't seem to find anything regarding these things.
In your serial.module.ts, create your custom serial handler service by custom factory providers
import { SerialHandlerService } from './serial-handler.service';
#Module({
providers: [
{
provide: 'SerialHandlerService',
useFactory: SerialHandlerService,
},
],
})
export class SerialModule {}
Create serial-handler.service.ts in same folder
import * as SerialPort from 'serialport';
const Readline = SerialPort.parsers.Readline;
export const SerialHandlerService = () => {
const port = new SerialPort(
{YOUR_SERIAL_PORT},
{
baudRate: {YOUR_SERIAL_BOADRATE},
dataBits: {YOUR_SERIAL_DATABITS},
stopBits: {YOUR_SERIAL_STOPBITS},
parity: {YOUR_SERIAL_PARITY},
},
(err) => {
if (err) {
console.error(err)
// Handle Error
}
console.log('success')
},
);
// I'm using Readline parser here. But, You can change parser that you want!
const parser = new Readline({ delimiter: '\r\n' });
port.pipe(parser);
port.on('open', () => {
console.info('port opened');
});
parser.on('data', (data) => {
console.log(data);
// Data is string, process your data below!
}
}
Add your serial.module.ts in your app.module.ts
#Module({
imports: [
// your other modules...
SerialModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
[Reference: https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory]
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
In real app, we write:
export class AppModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
In e2e test, I do something like this:
const module = await Test.createTestingModule({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
}).compile();
app = await module.createNestApplication().init();
So how can I specific middleware in e2e test?
Maybe try to create a specific TestModule class only for e2e and provide it to the createTestingModule?
#Module({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
})
export class TestModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
And then in e2e:
const module = await Test.createTestingModule({
imports: [TestModule]
}).compile();
app = await module.createNestApplication().init();
I had similar problem, I needed to attach global middlewares. There is no info on the Internet about that as well, but by chance I've found the solution. Maybe someone will be looking for it, so here it is:
To use global middleware in e2e in NestJS:
Firstly create the app, but don't init it. Only compile:
const app = Test
.createTestingModule({ imports: [AppModule] })
.compile()
.createNestApplication();
After that you can add all your global middlewares:
app.enableCors();
app.use(json());
app.use(formDataMiddleware(config));
Now init the app and that's it:
await app.init();
You'll need to put app.use(new AuthMiddleware().use); before app.init().
describe('Module E2E', () => {
const mockedTest = {
create: jest.fn().mockImplementation((t) => Promise.resolve(t)),
};
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
controllers: [TestController],
providers: [
TestService, // the service contains a MySQL Model
{
provide: getModelToken(Test), // Test is the name of Model
useValue: mockedTest,
},
],
}).compile();
app = moduleRef.createNestApplication();
app.use(new AuthMiddleware().use); // auth middleware
await app.init();
});
});
I am using store in my application like below and it works fine.
export class NavigationComponent {
navigationLinks$: Observable<Navigation[]>;
constructor(private store: Store<State>) {
this.navigationLinks$ = this.store.select('navigation')
.map((result: State) => result.navigationLinks);
}
Now, I am trying to create a unit test and want to mock this store. This is what i am doing:
1. Creating the Mock Store
Creating a mock store which will return mock data when this.store.select('') is called. The mockdata returns a property of array type called navigationLinks.
class StoreMock {
public dispatch(obj) {
console.log('dispatching from the mock store!')
}
public select(obj) {
console.log('selecting from the mock store!');
return Observable.of([
{ 'navigaitonLinks$': [{ 'name': 'Help', hasChild: false}] }
])
}
}
2. BeforeEach blocks
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavigationComponent],
providers: [{provide: Store, useClass: StoreMock}],
imports: [
StoreModule.provideStore(reducers)
],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
3. My test
I know this test will fail as per the expect statement but I am not able to populate the navigationLinks$ property with my mock data.
it(`should create the navigation with 'Help' link`, () => {
let navLinks: any[];
component.navigationLinks$.subscribe();
console.log(navLinks); // This should print my mockdata so i can assert it
expect(component.navigationLinks$).toEqual('Help');
});
The console.log prints undefined and is not able to read the data that MockStore select() is returning. Is there something extra I need to do?
I have the same issue, and I just return the object with Observable.of() function.
return Observable.of([
{ 'navigaitonLinks$': [{ 'name': 'Help', hasChild: false}] }
])
to
return Observable.of([{ 'name': 'Help', hasChild: false}, {}, {} ... ]);
This will populate your Observable object :
it(`should create the navigation with 'Help' link`, () => {
component.navigationLinks$.subscribe((links) => {
console.log(links); // This should print an array of Links
});
});