Skip to content

Commit a1c8b1a

Browse files
committed
test: 상품 서비스, 주문 파사드 tc 추가 및 예외 구체화
1 parent de107f0 commit a1c8b1a

File tree

2 files changed

+197
-35
lines changed

2 files changed

+197
-35
lines changed

test/unit/order-facade.spec.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DataSource } from 'typeorm';
22
import { Test, TestingModule } from '@nestjs/testing';
33
import { OrderFacade } from '@domain/usecase';
44
import { OrderService, PointService, ProductService } from '@domain/services';
5+
import { BadRequestException, NotFoundException } from '@nestjs/common';
56

67
describe('OrderFacade', () => {
78
let orderFacade: OrderFacade;
@@ -164,15 +165,15 @@ describe('OrderFacade', () => {
164165
});
165166

166167
describe('createOrderWithTransaction: 주문 생성 실패', () => {
167-
it('모든 상품의 재고가 없으면 주문 생성이 실패하고 트랜잭션이 롤백되어야 한다.', async () => {
168+
it('모든 상품의 재고가 없으면 NotFoundException을 발생시키고, 트랜잭션이 롤백되어야 한다.', async () => {
168169
const userId = 1;
169170
const items = [
170171
{ productId: 1, quantity: 1 },
171172
{ productId: 2, quantity: 2 },
172173
];
173174

174-
productService.decrementStockWithLock.mockRejectedValueOnce(
175-
new Error('Out of stock'),
175+
productService.decrementStockWithLock.mockRejectedValue(
176+
new NotFoundException('상품 재고가 부족합니다.'),
176177
);
177178
productService.findProductsByIdsWithStock.mockResolvedValueOnce([]);
178179
pointService.usePointWithLock.mockResolvedValueOnce({
@@ -186,7 +187,7 @@ describe('OrderFacade', () => {
186187
userId,
187188
items,
188189
}),
189-
).rejects.toThrow(Error);
190+
).rejects.toThrow(new NotFoundException('상품 재고가 부족합니다.'));
190191

191192
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
192193
expect(queryRunner.release).toHaveBeenCalled();
@@ -219,6 +220,7 @@ describe('OrderFacade', () => {
219220
items,
220221
}),
221222
).rejects.toThrow(Error);
223+
222224
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
223225
expect(queryRunner.release).toHaveBeenCalled();
224226
});
@@ -244,6 +246,63 @@ describe('OrderFacade', () => {
244246
await expect(
245247
orderFacade.createOrderWithTransaction({ userId, items }),
246248
).rejects.toThrow(Error);
249+
250+
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
251+
expect(queryRunner.release).toHaveBeenCalled();
252+
});
253+
254+
it('포인트 부족 시 BadRequestException을 발생시키고, 트랜잭션이 롤백되어야 한다.', async () => {
255+
const userId = 1;
256+
const items = [
257+
{ productId: 1, quantity: 1 },
258+
{ productId: 2, quantity: 2 },
259+
];
260+
261+
productService.decrementStockWithLock.mockResolvedValueOnce({
262+
inStockProductIds: [1, 2],
263+
outOfStockProductIds: [],
264+
});
265+
productService.findProductsByIdsWithStock.mockResolvedValueOnce([
266+
{ id: 1, price: 100, quantity: 1 },
267+
{ id: 2, price: 200, quantity: 2 },
268+
]);
269+
pointService.usePointWithLock.mockRejectedValueOnce(
270+
new BadRequestException('잔액이 부족합니다.'),
271+
);
272+
orderService.createOrder.mockResolvedValueOnce({ id: 1, userId, items });
273+
274+
await expect(
275+
orderFacade.createOrderWithTransaction({ userId, items }),
276+
).rejects.toThrow(new BadRequestException('잔액이 부족합니다.'));
277+
278+
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
279+
expect(queryRunner.release).toHaveBeenCalled();
280+
});
281+
282+
it('유효하지 않은 사용자일 경우, NotFoundException을 발생시키고, 트랜잭션이 롤백되어야 한다.', async () => {
283+
const userId = 1;
284+
const items = [
285+
{ productId: 1, quantity: 1 },
286+
{ productId: 2, quantity: 2 },
287+
];
288+
289+
pointService.usePointWithLock.mockRejectedValueOnce(
290+
new NotFoundException('사용자를 찾을 수 없습니다.'),
291+
);
292+
productService.decrementStockWithLock.mockResolvedValueOnce({
293+
inStockProductIds: [1, 2],
294+
outOfStockProductIds: [],
295+
});
296+
productService.findProductsByIdsWithStock.mockResolvedValueOnce([
297+
{ id: 1, price: 100, quantity: 1 },
298+
{ id: 2, price: 200, quantity: 2 },
299+
]);
300+
orderService.createOrder.mockResolvedValueOnce({ id: 1, userId, items });
301+
302+
await expect(
303+
orderFacade.createOrderWithTransaction({ userId, items }),
304+
).rejects.toThrow(new NotFoundException('사용자를 찾을 수 없습니다.'));
305+
247306
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
248307
expect(queryRunner.release).toHaveBeenCalled();
249308
});

test/unit/product-service.spec.ts

Lines changed: 134 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { ProductService } from '@domain/services';
33
import { ProductRepository, StockRepository } from '@domain/repositories';
4+
import { OutOfStockException } from '@domain/exceptions';
5+
import { NotFoundException } from '@nestjs/common';
46

57
describe('ProductService', () => {
68
let productService: ProductService;
@@ -32,43 +34,144 @@ describe('ProductService', () => {
3234
productService = moduleFixture.get(ProductService);
3335
});
3436

35-
it('getProductByIdWithStock: 상품 아이디로 상품 조회 메서드를 호출해야 한다.', async () => {
36-
const productId = 1;
37-
const product = await productService.getProductByIdWithStock(productId);
37+
afterEach(() => {
38+
jest.clearAllMocks();
39+
});
40+
41+
describe('상품 조회 성공', () => {
42+
it('getProductByIdWithStock: 상품 아이디로 상품 조회 메서드를 호출해야 한다.', async () => {
43+
const productId = 1;
44+
const product = await productService.getProductByIdWithStock(productId);
45+
46+
expect(product.id).toBe(productId);
47+
expect(productRepository.getProductByIdWithStock).toHaveBeenCalledWith(
48+
productId,
49+
);
50+
expect(productRepository.getProductByIdWithStock).toHaveBeenCalledTimes(
51+
1,
52+
);
53+
});
54+
55+
it('findProductsByIdsWithStock: 상품 아이디 배열로 상품 조회 메서드를 호출해야 한다.', async () => {
56+
const productIds = [1, 2];
57+
const products =
58+
await productService.findProductsByIdsWithStock(productIds);
59+
60+
expect(products.length).toBe(productIds.length);
61+
expect(productRepository.findProductsByIdsWithStock).toHaveBeenCalledWith(
62+
productIds,
63+
);
64+
expect(
65+
productRepository.findProductsByIdsWithStock,
66+
).toHaveBeenCalledTimes(1);
67+
});
68+
69+
it('findProductsByIdsWithStock: 상품이 없을 떄 빈 배열을 반환해야 한다.', async () => {
70+
const productIds = [1, 2];
71+
productRepository.findProductsByIdsWithStock.mockResolvedValueOnce([]);
72+
const products =
73+
await productService.findProductsByIdsWithStock(productIds);
74+
75+
expect(products.length).toBe(0);
76+
});
77+
});
78+
79+
describe('상품 조회 실패', () => {
80+
it('getProductByIdWithStock: 상품 조회 에러 발생 시 에러가 발생해야 한다.', async () => {
81+
productRepository.getProductByIdWithStock.mockRejectedValueOnce(
82+
new Error(),
83+
);
84+
const productId = 1;
85+
86+
await expect(
87+
productService.getProductByIdWithStock(productId),
88+
).rejects.toThrow();
89+
});
3890

39-
expect(product.id).toBe(productId);
40-
expect(productRepository.getProductByIdWithStock).toHaveBeenCalledWith(
41-
productId,
42-
);
43-
expect(productRepository.getProductByIdWithStock).toHaveBeenCalledTimes(1);
91+
it('getProductByIdWithStock: 상품이 없을 때 NotFoundException이 발생해야 한다.', async () => {
92+
productRepository.getProductByIdWithStock.mockResolvedValueOnce(null);
93+
const productId = 1;
94+
95+
await expect(
96+
productService.getProductByIdWithStock(productId),
97+
).rejects.toThrow(NotFoundException);
98+
});
99+
100+
it('findProductsByIdsWithStock: 상품 조회 에러 발생 시 에러가 발생해야 한다.', async () => {
101+
productRepository.findProductsByIdsWithStock.mockRejectedValueOnce(
102+
new Error(),
103+
);
104+
const productIds = [1, 2];
105+
106+
await expect(
107+
productService.findProductsByIdsWithStock(productIds),
108+
).rejects.toThrow(new Error());
109+
});
44110
});
45111

46-
it('findProductsByIdsWithStock: 상품 아이디 배열로 상품 조회 메서드를 호출해야 한다.', async () => {
47-
const productIds = [1, 2];
48-
const products =
49-
await productService.findProductsByIdsWithStock(productIds);
50-
51-
expect(products.length).toBe(productIds.length);
52-
expect(productRepository.findProductsByIdsWithStock).toHaveBeenCalledWith(
53-
productIds,
54-
);
55-
expect(productRepository.findProductsByIdsWithStock).toHaveBeenCalledTimes(
56-
1,
57-
);
112+
describe('상품 재고 차감 성공', () => {
113+
it('decrementStockWithLock: 주문 상품 갯수만큼 상품 재고 차감 메서드를 호출해야 한다.', async () => {
114+
const items = [
115+
{ productId: 1, quantity: 1 },
116+
{ productId: 2, quantity: 2 },
117+
];
118+
await productService.decrementStockWithLock({
119+
items,
120+
queryRunner: null,
121+
});
122+
123+
expect(stockRepository.decrementStockWithLock).toHaveBeenCalledTimes(
124+
items.length,
125+
);
126+
});
127+
128+
it('decrementStockWithLock: 재고가 있는 상품과 없는 상품을 구분하여 반환한다.', async () => {
129+
const items = [
130+
{ productId: 1, quantity: 0 },
131+
{ productId: 2, quantity: 2 },
132+
];
133+
stockRepository.decrementStockWithLock.mockRejectedValueOnce(
134+
new OutOfStockException(),
135+
);
136+
137+
const res = await productService.decrementStockWithLock({
138+
items,
139+
queryRunner: null,
140+
});
141+
142+
expect(res.inStockProductIds).toEqual([2]);
143+
expect(res.outOfStockProductIds).toEqual([1]);
144+
});
58145
});
59146

60-
it('decrementStockWithLock: 주문 상품 갯수만큼 상품 재고 차감 메서드를 호출해야 한다.', async () => {
61-
const items = [
62-
{ productId: 1, quantity: 1 },
63-
{ productId: 2, quantity: 2 },
64-
];
65-
await productService.decrementStockWithLock({
66-
items,
67-
queryRunner: null,
147+
describe('상품 재고 차감 실패', () => {
148+
it('decrementStockWithLock: 상품 재고 차감 에러 발생 시 에러가 발생해야 한다.', async () => {
149+
stockRepository.decrementStockWithLock.mockRejectedValueOnce(new Error());
150+
const items = [{ productId: 1, quantity: 1 }];
151+
152+
await expect(
153+
productService.decrementStockWithLock({
154+
items,
155+
queryRunner: null,
156+
}),
157+
).rejects.toThrow();
68158
});
69159

70-
expect(stockRepository.decrementStockWithLock).toHaveBeenCalledTimes(
71-
items.length,
72-
);
160+
it('decrementStockWithLock: 모든 상품이 재고가 없을 때 NotFoundException이 발생해야 한다.', async () => {
161+
const items = [
162+
{ productId: 1, quantity: 1 },
163+
{ productId: 2, quantity: 1 },
164+
];
165+
stockRepository.decrementStockWithLock.mockRejectedValue(
166+
new OutOfStockException(),
167+
);
168+
169+
await expect(
170+
productService.decrementStockWithLock({
171+
items,
172+
queryRunner: null,
173+
}),
174+
).rejects.toThrow(new NotFoundException('상품 재고가 부족합니다.'));
175+
});
73176
});
74177
});

0 commit comments

Comments
 (0)