Skip to content

Commit

Permalink
Merge pull request #123 from zhumeisongsong/feature/refactor-user-ser…
Browse files Browse the repository at this point in the history
…vice

refactor: ♻️ user module
  • Loading branch information
zhumeisongsong authored Dec 17, 2024
2 parents d515c00 + 9504bdb commit 0a380b0
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException } from '@nestjs/common';
import { GetUserByEmailUseCase } from './get-user-by-email.use-case';
import { UsersRepository } from '@users/domain';
import { UsersService } from '../users.service';

describe('GetUserByEmailUseCase', () => {
let getUserByEmailUseCase: GetUserByEmailUseCase;
let usersRepository: UsersRepository;

beforeEach(() => {
usersRepository = {
findOneById: jest.fn(),
findOneByEmail: jest.fn(),
};
getUserByEmailUseCase = new GetUserByEmailUseCase(usersRepository);
let useCase: GetUserByEmailUseCase;
let usersService: UsersService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
GetUserByEmailUseCase,
{
provide: UsersService,
useValue: {
findOneByEmail: jest.fn(),
},
},
],
}).compile();

useCase = module.get<GetUserByEmailUseCase>(GetUserByEmailUseCase);
usersService = module.get<UsersService>(UsersService);
});

it('should be defined', () => {
expect(useCase).toBeDefined();
});

describe('execute', () => {
it('should return user when found', async () => {
const mockUser = {
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
};
(usersRepository.findOneByEmail as jest.Mock).mockResolvedValue(mockUser);

const result = await getUserByEmailUseCase.execute('[email protected]');

expect(result).toEqual(mockUser);
expect(usersRepository.findOneByEmail).toHaveBeenCalledWith(
'[email protected]',
);
});
it('should return a user when found', async () => {
const mockUser = { id: '1', email: '[email protected]', firstName: 'John', lastName: 'Doe' };
jest.spyOn(usersService, 'findOneByEmail').mockResolvedValue(mockUser);

it('should return null when user not found', async () => {
(usersRepository.findOneByEmail as jest.Mock).mockResolvedValue(null);
const result = await useCase.execute('[email protected]');

expect(result).toBe(mockUser);
expect(usersService.findOneByEmail).toHaveBeenCalledWith('[email protected]');
});

const result = await getUserByEmailUseCase.execute('[email protected]');
it('should throw NotFoundException when user is not found', async () => {
jest.spyOn(usersService, 'findOneByEmail').mockRejectedValue(new NotFoundException());

expect(result).toBeNull();
expect(usersRepository.findOneByEmail).toHaveBeenCalledWith(
'[email protected]',
);
await expect(useCase.execute('[email protected]')).rejects.toThrow(NotFoundException);
expect(usersService.findOneByEmail).toHaveBeenCalledWith('[email protected]');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Inject, Injectable } from "@nestjs/common";
import { User, USERS_REPOSITORY, UsersRepository } from "@users/domain";
import { Injectable } from "@nestjs/common";
import { User } from "@users/domain";
import { UsersService } from "../users.service";

@Injectable()
export class GetUserByEmailUseCase {
constructor(
@Inject(USERS_REPOSITORY)
private readonly usersRepository: UsersRepository,
private readonly usersService: UsersService,
) {}

async execute(email: string): Promise<User | null> {
return this.usersRepository.findOneByEmail(email);
async execute(email: string): Promise<User> {
return this.usersService.findOneByEmail(email);
}
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException } from '@nestjs/common';
import { GetUserByIdUseCase } from './get-user-by-id.use-case';
import { UsersRepository } from '@users/domain';
import { UsersService } from '../users.service';

describe('GetUserByIdUseCase', () => {
let getUserByIdUseCase: GetUserByIdUseCase;
let usersRepository: UsersRepository;

beforeEach(() => {
usersRepository = {
findOneById: jest.fn(),
findOneByEmail: jest.fn(),
};
getUserByIdUseCase = new GetUserByIdUseCase(usersRepository);
let useCase: GetUserByIdUseCase;
let usersService: UsersService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
GetUserByIdUseCase,
{
provide: UsersService,
useValue: {
findOneById: jest.fn(),
},
},
],
}).compile();

useCase = module.get<GetUserByIdUseCase>(GetUserByIdUseCase);
usersService = module.get<UsersService>(UsersService);
});

it('should be defined', () => {
expect(useCase).toBeDefined();
});

describe('execute', () => {
it('should return user when found', async () => {
const mockUser = {
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
};
(usersRepository.findOneById as jest.Mock).mockResolvedValue(mockUser);

const result = await getUserByIdUseCase.execute('1');

expect(result).toEqual(mockUser);
expect(usersRepository.findOneById).toHaveBeenCalledWith('1');
});
it('should return a user when found', async () => {
const mockUser = { id: '1', email: '[email protected]', firstName: 'John', lastName: 'Doe' };
jest.spyOn(usersService, 'findOneById').mockResolvedValue(mockUser);

it('should return null when user not found', async () => {
(usersRepository.findOneById as jest.Mock).mockResolvedValue(null);
const result = await useCase.execute('1');

expect(result).toBe(mockUser);
expect(usersService.findOneById).toHaveBeenCalledWith('1');
});

const result = await getUserByIdUseCase.execute('1');
it('should throw NotFoundException when user is not found', async () => {
jest.spyOn(usersService, 'findOneById').mockRejectedValue(new NotFoundException());

expect(result).toBeNull();
expect(usersRepository.findOneById).toHaveBeenCalledWith('1');
await expect(useCase.execute('1')).rejects.toThrow(NotFoundException);
expect(usersService.findOneById).toHaveBeenCalledWith('1');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { User, USERS_REPOSITORY, UsersRepository } from '@users/domain';
import { Injectable } from '@nestjs/common';
import { User } from '@users/domain';

import { UsersService } from '../users.service';

@Injectable()
export class GetUserByIdUseCase {
constructor(
@Inject(USERS_REPOSITORY)
private readonly usersRepository: UsersRepository,
) {}
constructor(private readonly usersService: UsersService) {}

async execute(id: string): Promise<User | null> {
return await this.usersRepository.findOneById(id);
async execute(id: string): Promise<User> {
return await this.usersService.findOneById(id);
}
}
92 changes: 40 additions & 52 deletions libs/users/application/src/lib/users.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,75 @@
import { Test, TestingModule } from '@nestjs/testing';

import { NotFoundException } from '@nestjs/common';
import { UsersService } from './users.service';
import { GetUserByIdUseCase } from './use-cases/get-user-by-id.use-case';
import { GetUserByEmailUseCase } from './use-cases/get-user-by-email.use-case';
import { USERS_REPOSITORY } from '@users/domain';
import { userError } from '@zhumeisong/common-error-exception';

describe('UsersService', () => {
let service: UsersService;
let getUserByIdUseCase: GetUserByIdUseCase;
let getUserByEmailUseCase: GetUserByEmailUseCase;
let usersRepository: any;

beforeEach(async () => {
usersRepository = {
findOneById: jest.fn(),
findOneByEmail: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: GetUserByIdUseCase,
useValue: {
execute: jest.fn(),
},
},
{
provide: GetUserByEmailUseCase,
useValue: {
execute: jest.fn(),
},
provide: USERS_REPOSITORY,
useValue: usersRepository,
},
],
}).compile();

service = module.get<UsersService>(UsersService);
getUserByIdUseCase = module.get<GetUserByIdUseCase>(GetUserByIdUseCase);
getUserByEmailUseCase = module.get<GetUserByEmailUseCase>(GetUserByEmailUseCase);
});

describe('getUserById', () => {
it('should return user when found', async () => {
const mockUser = {
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
};
(getUserByIdUseCase.execute as jest.Mock).mockResolvedValue(mockUser);
it('should be defined', () => {
expect(service).toBeDefined();
});

describe('findOneById', () => {
it('should return a user when found', async () => {
const mockUser = { id: '1', email: '[email protected]', firstName: 'John', lastName: 'Doe' };
usersRepository.findOneById.mockResolvedValue(mockUser);

const result = await service.findOneById('1');

expect(result).toEqual(mockUser);
expect(getUserByIdUseCase.execute).toHaveBeenCalledWith('1');
expect(result).toBe(mockUser);
expect(usersRepository.findOneById).toHaveBeenCalledWith('1');
});

it('should return null when user not found', async () => {
(getUserByIdUseCase.execute as jest.Mock).mockResolvedValue(null);

const result = await service.findOneById('1');
it('should throw NotFoundException when user is not found', async () => {
usersRepository.findOneById.mockResolvedValue(null);

expect(result).toBeNull();
expect(getUserByIdUseCase.execute).toHaveBeenCalledWith('1');
await expect(service.findOneById('1')).rejects.toThrow(
new NotFoundException(userError.NOT_FOUND)
);
expect(usersRepository.findOneById).toHaveBeenCalledWith('1');
});
});

describe('getUserByEmail', () => {
it('should return user when found', async () => {
const mockUser = {
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
};
(getUserByEmailUseCase.execute as jest.Mock).mockResolvedValue(mockUser);
describe('findOneByEmail', () => {
it('should return a user when found', async () => {
const mockUser = { id: '1', email: '[email protected]', firstName: 'John', lastName: 'Doe' };
usersRepository.findOneByEmail.mockResolvedValue(mockUser);

const result = await service.findOneByEmail('test@example.com');
const result = await service.findOneByEmail('test@test.com');

expect(result).toEqual(mockUser);
expect(getUserByEmailUseCase.execute).toHaveBeenCalledWith('test@example.com');
expect(result).toBe(mockUser);
expect(usersRepository.findOneByEmail).toHaveBeenCalledWith('test@test.com');
});

it('should return null when user not found', async () => {
(getUserByEmailUseCase.execute as jest.Mock).mockResolvedValue(null);

const result = await service.findOneByEmail('[email protected]');
it('should throw NotFoundException when user is not found', async () => {
usersRepository.findOneByEmail.mockResolvedValue(null);

expect(result).toBeNull();
expect(getUserByEmailUseCase.execute).toHaveBeenCalledWith('[email protected]');
await expect(service.findOneByEmail('[email protected]')).rejects.toThrow(
new NotFoundException(userError.NOT_FOUND)
);
expect(usersRepository.findOneByEmail).toHaveBeenCalledWith('[email protected]');
});
});
});
30 changes: 19 additions & 11 deletions libs/users/application/src/lib/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { Injectable } from '@nestjs/common';
import { User } from '@users/domain';

import { GetUserByIdUseCase } from './use-cases/get-user-by-id.use-case';
import { GetUserByEmailUseCase } from './use-cases/get-user-by-email.use-case';
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { User, USERS_REPOSITORY, UsersRepository } from '@users/domain';
import { userError } from '@zhumeisong/common-error-exception';

@Injectable()
export class UsersService {
constructor(
private readonly getUserByIdUseCase: GetUserByIdUseCase,
private readonly getUserByEmailUseCase: GetUserByEmailUseCase,
@Inject(USERS_REPOSITORY)
private readonly usersRepository: UsersRepository,
) {}

async findOneById(id: string): Promise<User | null> {
return this.getUserByIdUseCase.execute(id);
async findOneById(id: string): Promise<User> {
const user = await this.usersRepository.findOneById(id);
if (!user) {
throw new NotFoundException(userError.NOT_FOUND);
}

return user;
}

async findOneByEmail(email: string): Promise<User | null> {
return this.getUserByEmailUseCase.execute(email);
async findOneByEmail(email: string): Promise<User> {
const user = await this.usersRepository.findOneByEmail(email);
if (!user) {
throw new NotFoundException(userError.NOT_FOUND);
}

return user;
}
}
2 changes: 1 addition & 1 deletion libs/users/domain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './lib/user.entity';
export * from './lib/users.repository';
export * from './lib/users-repository.interface';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UsersRepository } from './users.repository';
import { UsersRepository } from './users-repository.interface';
import { User } from './user.entity';

class MockUsersRepository implements UsersRepository {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@nestjs/mongoose": "^10.1.0",
"@nestjs/platform-express": "^10.4.7",
"@nestjs/throttler": "^6.2.1",
"@zhumeisong/common-error-exception": "^1.0.2",
"aws-sdk": "^2.1692.0",
"axios": "^1.7.7",
"class-validator": "^0.14.1",
Expand Down
Loading

0 comments on commit 0a380b0

Please sign in to comment.