Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip(mongo-sample): rearrange sample - add in-memory for e2e, mockingbird and dto's #1221

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/mongo-sample/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatModule } from './cat/cat.module';
import { MongooseModuleConfigService } from './config/mongoose-module-config.service';

@Module({
imports: [MongooseModule.forRoot('mongodb://localhost:27017'), CatModule],
imports: [
MongooseModule.forRootAsync({ useClass: MongooseModuleConfigService }),
CatModule,
],
controllers: [AppController],
providers: [AppService],
})
Expand Down
6 changes: 0 additions & 6 deletions apps/mongo-sample/src/cat/cat.dto.ts

This file was deleted.

6 changes: 3 additions & 3 deletions apps/mongo-sample/src/cat/cat.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { CatController } from './cat.controller';
import { CatService } from './cat.service';
import { MongooseModule } from '@nestjs/mongoose';
import { CatSchema } from './schemas/cat.schema';
import { CatController } from './controller/cat.controller';
import { CatService } from './cat.service';
import { CatSchema } from './schemas/cat.document';

@Module({
imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])],
Expand Down
87 changes: 62 additions & 25 deletions apps/mongo-sample/src/cat/cat.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,57 @@
* and add your fields on top. Seriously, 59 plus fields is a lot.
*/

import { Model, Query } from 'mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { CatService } from './cat.service';
import { createMock } from '@golevelup/nestjs-testing';
import { getModelToken } from '@nestjs/mongoose';
import { CatService } from './cat.service';
import { Cat } from './interfaces/cat.interface';
import { createMock } from '@golevelup/nestjs-testing';
import { Model, Query } from 'mongoose';
import { CatDoc } from './interfaces/cat-document.interface';
import { CatDocument } from './schemas/cat.document';

const lasagna = 'lasagna lover';

// I'm lazy and like to have functions that can be re-used to deal with a lot of my initialization/creation logic
const mockCat = (
const mockCat: (
name?: string,
id?: string,
age?: number,
breed?: string,
) => Cat = (
name = 'Ventus',
id = 'a uuid',
age = 4,
breed = 'Russian Blue',
): Cat => ({
name,
id,
age,
breed,
});
) => {
return {
name,
id,
age,
breed,
};
};

// still lazy, but this time using an object instead of multiple parameters
const mockCatDoc = (mock?: Partial<Cat>): Partial<CatDoc> => ({
name: mock?.name || 'Ventus',
_id: mock?.id || 'a uuid',
age: mock?.age || 4,
breed: mock?.breed || 'Russian Blue',
});
const mockCatDoc: (mock?: {
name?: string;
id?: string;
breed?: string;
age?: number;
}) => Partial<CatDocument> = (mock?: {
name: string;
id: string;
age: number;
breed: string;
}) => {
return {
name: (mock && mock.name) || 'Ventus',
_id: (mock && mock.id) || 'a uuid',
age: (mock && mock.age) || 4,
breed: (mock && mock.breed) || 'Russian Blue',
};
};
Comment on lines +23 to +60
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this change back to the shorter version?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by 'shorter version'?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const mockCatDoc = (mock?: Partial<Cat>): Partial<CatDoc> => ({
  name: mock?.name || 'Ventus',
  _id: mock?.id || 'a uuid',
  age: mock?.age || 4,
  breed: mock?.breed || 'Russian Blue',
});

Instead of

const mockCatDoc: (mock?: {
  name?: string;
  id?: string;
  breed?: string;
  age?: number;
}) => Partial<CatDocument> = (mock?: {
  name: string;
  id: string;
  age: number;
  breed: string;
}) => {
  return {
    name: (mock && mock.name) || 'Ventus',
    _id: (mock && mock.id) || 'a uuid',
    age: (mock && mock.age) || 4,
    breed: (mock && mock.breed) || 'Russian Blue',
  };
};


const catArray = [
const catArray: Cat[] = [
mockCat(),
mockCat('Vitani', 'a new uuid', 2, 'Tabby'),
mockCat('Simba', 'the king', 14, 'Lion'),
Expand All @@ -54,7 +73,7 @@ const catDocArray = [

describe('CatService', () => {
let service: CatService;
let model: Model<CatDoc>;
let model: Model<CatDocument>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -78,7 +97,7 @@ describe('CatService', () => {
}).compile();

service = module.get<CatService>(CatService);
model = module.get<Model<CatDoc>>(getModelToken('Cat'));
model = module.get<Model<CatDocument>>(getModelToken('Cat'));
});

it('should be defined', () => {
Expand All @@ -97,55 +116,67 @@ describe('CatService', () => {
jest.spyOn(model, 'find').mockReturnValue({
exec: jest.fn().mockResolvedValueOnce(catDocArray),
} as any);

const cats = await service.getAll();

expect(cats).toEqual(catArray);
});

it('should getOne by id', async () => {
jest.spyOn(model, 'findOne').mockReturnValueOnce(
createMock<Query<CatDoc, CatDoc>>({
createMock<Query<CatDocument, CatDocument>>({
exec: jest
.fn()
.mockResolvedValueOnce(mockCatDoc({ name: 'Ventus', id: 'an id' })),
}),
);

const findMockCat = mockCat('Ventus', 'an id');
const foundCat = await service.getOne('an id');

expect(foundCat).toEqual(findMockCat);
});

it('should getOne by name', async () => {
jest.spyOn(model, 'findOne').mockReturnValueOnce(
createMock<Query<CatDoc, CatDoc>>({
createMock<Query<CatDocument, CatDocument>>({
exec: jest
.fn()
.mockResolvedValueOnce(
mockCatDoc({ name: 'Mufasa', id: 'the dead king' }),
),
}),
);

const findMockCat = mockCat('Mufasa', 'the dead king');
const foundCat = await service.getOneByName('Mufasa');

expect(foundCat).toEqual(findMockCat);
});

it('should insert a new cat', async () => {
jest.spyOn(model, 'create').mockImplementationOnce(() =>
Promise.resolve({
_id: 'some id',
id: 'some id',
name: 'Oliver',
age: 1,
breed: 'Tabby',
}),
);

const newCat = await service.insertOne({
name: 'Oliver',
age: 1,
breed: 'Tabby',
});

expect(newCat).toEqual(mockCat('Oliver', 'some id', 1, 'Tabby'));
});
// jest is complaining about findOneAndUpdate. Can't say why at the moment.

it.skip('should update a cat successfully', async () => {
jest.spyOn(model, 'findOneAndUpdate').mockReturnValueOnce(
createMock<Query<CatDoc, CatDoc>>({
createMock<Query<CatDocument, CatDocument>>({
exec: jest.fn().mockResolvedValueOnce({
_id: lasagna,
name: 'Garfield',
Expand All @@ -154,22 +185,28 @@ describe('CatService', () => {
}),
}),
);

const updatedCat = await service.updateOne({
_id: lasagna,
name: 'Garfield',
breed: 'Tabby',
age: 42,
});

expect(updatedCat).toEqual(mockCat('Garfield', lasagna, 42, 'Tabby'));
});

it('should delete a cat successfully', async () => {
// really just returning a truthy value here as we aren't doing any logic with the return
jest.spyOn(model, 'remove').mockResolvedValueOnce(true as any);
jest.spyOn(model, 'remove').mockResolvedValueOnce(true);

expect(await service.deleteOne('a bad id')).toEqual({ deleted: true });
});

it('should not delete a cat', async () => {
// really just returning a falsy value here as we aren't doing any logic with the return
jest.spyOn(model, 'remove').mockRejectedValueOnce(new Error('Bad delete'));

expect(await service.deleteOne('a bad id')).toEqual({
deleted: false,
message: 'Bad delete',
Expand Down
55 changes: 13 additions & 42 deletions apps/mongo-sample/src/cat/cat.service.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,26 @@
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { CatDTO } from './cat.dto';
import { Cat } from './interfaces/cat.interface';
import { CatDoc } from './interfaces/cat-document.interface';
import { CatDocument } from './schemas/cat.document';
import { CreateCatDto, UpdateCatDto } from './dto/cat-dto';

@Injectable()
export class CatService {
constructor(@InjectModel('Cat') private readonly catModel: Model<CatDoc>) {}
constructor(
@InjectModel('Cat') private readonly catModel: Model<CatDocument>,
) {}

async getAll(): Promise<Cat[]> {
const catDocs = await this.catModel.find().exec();
return catDocs.map((doc) => ({
id: doc._id,
age: doc.age,
name: doc.name,
breed: doc.breed,
}));
return this.catModel.find().lean().exec();
}

async getOne(id: string): Promise<Cat> {
const cat = await this.catModel.findOne({ _id: id }).exec();
return {
id: cat._id,
age: cat.age,
breed: cat.breed,
name: cat.name,
};
return this.catModel.findOne({ _id: id }).exec();
}

async getOneByName(name: string): Promise<Cat> {
const cat = await this.catModel.findOne({ name }).exec();
return {
id: cat._id,
age: cat.age,
breed: cat.breed,
name: cat.name,
};
return this.catModel.findOne({ name }).exec();
}

/**
Expand All @@ -45,30 +29,17 @@ export class CatService {
* Instead, you can use the class method `create` to achieve
* the same effect.
*/
async insertOne(cat: CatDTO): Promise<Cat> {
const retCat = await this.catModel.create(cat as any);
return {
id: retCat._id,
age: retCat.age,
name: retCat.name,
breed: retCat.breed,
};
async insertOne(cat: CreateCatDto): Promise<Cat> {
return this.catModel.create(cat);
}

async updateOne(cat: CatDTO): Promise<Cat> {
async updateOne(cat: UpdateCatDto): Promise<Cat> {
const { _id } = cat;
const foundCat = await this.catModel.findOneAndUpdate({ _id }, cat).exec();
return {
id: foundCat._id,
age: foundCat.age,
breed: foundCat.breed,
name: foundCat.name,
};
return this.catModel.findOneAndUpdate({ _id }, cat).exec();
}

async deleteOne(id: string): Promise<{ deleted: boolean; message?: string }> {
try {
// tslint:disable-next-line: no-invalid-await
await this.catModel.remove({ id });
return { deleted: true };
} catch (err) {
Expand Down
Loading