Skip to content

Commit

Permalink
test: Stubbing mail send service test
Browse files Browse the repository at this point in the history
  • Loading branch information
ing9990 committed Nov 2, 2023
1 parent 5fd9ce4 commit d353416
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package sample.cafekiosk.spring.api.service.mail;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import sample.cafekiosk.spring.client.mail.MailSendClient;
import sample.cafekiosk.spring.domain.history.mail.MailSendHistory;
import sample.cafekiosk.spring.domain.history.mail.MailSendHistoryRepository;

@Service
@RequiredArgsConstructor
public class MailService {

private final MailSendClient mailSendClient;
private final MailSendHistoryRepository mailSendHistoryRepository;

public boolean sendMail(String fromEmail, String toEmail, String subject, String content) {
boolean result = mailSendClient.sendEmail(fromEmail, toEmail, subject, content);

if (result) {
mailSendHistoryRepository.save(MailSendHistory.builder()
.fromMail(fromEmail)
.toEmail(toEmail)
.subject(subject)
.content(content)
.build());
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public OrderResponse createOrder(OrderCreateServiceRequest request, LocalDateTim

deductStockQuantities(products);

Order order = Order.create(products, registeredDateTime);
Order order = Order.create(registeredDateTime, products);
Order savedOrder = orderRepository.save(order);

return OrderResponse.of(savedOrder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package sample.cafekiosk.spring.api.service.order;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sample.cafekiosk.spring.api.service.mail.MailService;
import sample.cafekiosk.spring.domain.order.Order;
import sample.cafekiosk.spring.domain.order.OrderRepository;
import sample.cafekiosk.spring.domain.order.OrderStatus;

import java.time.LocalDate;
import java.util.List;

@Service
@RequiredArgsConstructor
public class OrderStatisticsService {

private final MailService mailService;
private final OrderRepository orderRepository;

public boolean sendOrderStatisticsMail(LocalDate orderDate, String email) {
// 해당 일자에 결제완료된 주문들을 가져와서
List<Order> orders = orderRepository.findOrdersBy(
orderDate.atStartOfDay(),
orderDate.plusDays(1).atStartOfDay(),
OrderStatus.PAYMENT_COMPLETED
);
// 총 매출 합계를 계산하고

int totalAmount = orders.stream().mapToInt(Order::getTotalPrice).sum();

// 메일 전송
boolean result = mailService.sendMail("[email protected]",
email,
String.format("[매출통계] %s", orderDate),
String.format("총 매출 합계는 %s원입니다.", totalAmount)
);

if (!result) {
throw new IllegalArgumentException("매출 통계 메일 전송에 실패했습니다.");
}

return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package sample.cafekiosk.spring.client.mail;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class MailSendClient {
public boolean sendEmail(String fromEmail, String toEmail, String subject, String content) {
log.info("메일 전송");
throw new IllegalArgumentException("메일 전송");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sample.cafekiosk.spring.domain.history.mail;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sample.cafekiosk.spring.domain.BaseEntity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Getter
@NoArgsConstructor
@Entity
public class MailSendHistory extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String fromMail;

private String toEmail;
private String subject;
private String content;

@Builder
private MailSendHistory(String fromMail, String toEmail, String subject, String content) {
this.fromMail = fromMail;
this.toEmail = toEmail;
this.subject = subject;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package sample.cafekiosk.spring.domain.history.mail;

import org.springframework.data.jpa.repository.JpaRepository;

public interface MailSendHistoryRepository extends JpaRepository<MailSendHistory, Long> {
}
17 changes: 12 additions & 5 deletions src/main/java/sample/cafekiosk/spring/domain/order/Order.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sample.cafekiosk.spring.domain.order;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sample.cafekiosk.spring.domain.BaseEntity;
Expand All @@ -26,7 +27,7 @@ public class Order extends BaseEntity {
private Long id;

@Enumerated(EnumType.STRING)
private OrderStatus status;
private OrderStatus orderStatus;

@OneToMany(mappedBy = "order", cascade = ALL)
private List<OrderProduct> orderProducts = new ArrayList<>();
Expand All @@ -35,18 +36,24 @@ public class Order extends BaseEntity {

private LocalDateTime registredDateTime;

public static Order create(List<Product> products, LocalDateTime registeredDateTime) {
return new Order(products, registeredDateTime);
public static Order create(LocalDateTime registeredDateTime, List<Product> products) {
return Order.builder()
.orderStatus(OrderStatus.INIT)
.products(products)
.registeredDateTime(registeredDateTime)
.build();
}

public Order(List<Product> products, LocalDateTime registeredDateTime) {
this.status = OrderStatus.INIT;
@Builder
private Order(List<Product> products, OrderStatus orderStatus, LocalDateTime registeredDateTime) {
this.orderStatus = orderStatus;
this.totalPrice = calculateTotalPrice(products);
this.registredDateTime = registeredDateTime;
this.orderProducts = products.stream().map(product -> new OrderProduct(this, product))
.collect(Collectors.toList());
}


private int calculateTotalPrice(List<Product> products) {
return products.stream().mapToInt(Product::getPrice).sum();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package sample.cafekiosk.spring.domain.order;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

@Query("select o from Order o where o.registredDateTime >= ?1 and o.registredDateTime < ?2 and o.orderStatus = ?3")
List<Order> findOrdersBy(LocalDateTime startDateTime, LocalDateTime endDateTime, OrderStatus orderStatus);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package sample.cafekiosk.spring.api.service.order;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import sample.cafekiosk.spring.client.mail.MailSendClient;
import sample.cafekiosk.spring.domain.history.mail.MailSendHistory;
import sample.cafekiosk.spring.domain.history.mail.MailSendHistoryRepository;
import sample.cafekiosk.spring.domain.order.Order;
import sample.cafekiosk.spring.domain.order.OrderRepository;
import sample.cafekiosk.spring.domain.order.OrderStatus;
import sample.cafekiosk.spring.domain.orderproduct.OrderProductRepository;
import sample.cafekiosk.spring.domain.product.Product;
import sample.cafekiosk.spring.domain.product.ProductRepository;
import sample.cafekiosk.spring.domain.product.ProductType;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static sample.cafekiosk.spring.domain.product.ProductSellingStatus.SELLING;
import static sample.cafekiosk.spring.domain.product.ProductType.HANDMADE;

@SpringBootTest
class OrderStatisticsServiceTest {

@Autowired
private OrderStatisticsService orderStatisticsService;

@Autowired
private OrderProductRepository orderProductRepository;

@Autowired
private OrderRepository orderRepository;

@Autowired
private ProductRepository productRepository;

@Autowired
private MailSendHistoryRepository mailSendHistoryRepository;

@MockBean
private MailSendClient mailSendClient;

@AfterEach
void tearDown() {
orderProductRepository.deleteAllInBatch();
orderRepository.deleteAllInBatch();
productRepository.deleteAllInBatch();
mailSendHistoryRepository.deleteAllInBatch();
}

@DisplayName("결제완료 주문들을 조회하여 매출 통계 메일을 전송한다.")
@Test
void test() {
// given
LocalDateTime now = LocalDateTime.of(2023, 3, 5, 0, 0);
Product product1 = createProduct(HANDMADE, "001", 1000);
Product product2 = createProduct(HANDMADE, "002", 2000);
Product product3 = createProduct(HANDMADE, "003", 3000);
List<Product> products = List.of(product1, product2, product3);
productRepository.saveAll(products);

Order order1 = createPaymentCompletedOrder(LocalDateTime.of(2023, 3, 4, 23, 59, 59), products);
Order order2 = createPaymentCompletedOrder(now, products);
Order order3 = createPaymentCompletedOrder(LocalDateTime.of(2023, 3, 6, 23, 59, 59), products);
Order order4 = createPaymentCompletedOrder(LocalDateTime.of(2023, 3, 6, 0, 0), products);

// stubbing
when(mailSendClient.sendEmail(any(String.class), any(String.class), any(String.class), any(String.class)))
.thenReturn(true);

// when
boolean result = orderStatisticsService.sendOrderStatisticsMail(LocalDate.of(2023, 3, 5), "[email protected]");

// then
assertThat(result).isTrue();

List<MailSendHistory> histories = mailSendHistoryRepository.findAll();
assertThat(histories).hasSize(1)
.extracting("content")
.contains("총 매출 합계는 6000원입니다.");

}

private Order createPaymentCompletedOrder(LocalDateTime now, List<Product> products) {
return orderRepository.save(Order.builder()
.registeredDateTime(now)
.orderStatus(OrderStatus.PAYMENT_COMPLETED)
.products(products)
.build());
}

private Product createProduct(ProductType type, String productNumber, int price) {
return Product.builder()
.productNumber(productNumber)
.type(type)
.sellingStatus(SELLING)
.name("메뉴 이름")
.price(price)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sample.cafekiosk.spring.domain.order;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import sample.cafekiosk.spring.domain.product.Product;
import sample.cafekiosk.spring.domain.product.ProductRepository;
import sample.cafekiosk.spring.domain.product.ProductSellingStatus;
import sample.cafekiosk.spring.domain.product.ProductType;

import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.groups.Tuple.tuple;
import static sample.cafekiosk.spring.domain.order.OrderStatus.INIT;
import static sample.cafekiosk.spring.domain.product.ProductSellingStatus.*;
import static sample.cafekiosk.spring.domain.product.ProductType.HANDMADE;

@SpringBootTest
@Transactional
class OrderRepositoryTest {

@Autowired
private OrderRepository orderRepository;

@Autowired
private ProductRepository productRepository;

@DisplayName("특정 두 시간 사이에 특정 주문상태에 해당하는 주문을 가져올 수 있다.")
@Test
void findOrdersBy() {
// given
Product product1 = createProduct("001", HANDMADE, "아메리카노", HOLD, 6000);
Product product2 = createProduct("002", HANDMADE, "팥빙수", STOP_SELLING, 5000);
Product product3 = createProduct("003", HANDMADE, "카페라떼", SELLING, 4000);
productRepository.saveAll(List.of(product1, product2, product3));

orderRepository.save(Order.create(LocalDateTime.of(2023, 11, 1, 0, 0, 0), List.of(product1, product2)));
orderRepository.save(Order.create(LocalDateTime.of(2023, 12, 1, 0, 0, 0), List.of(product2, product3)));
orderRepository.save(Order.create(LocalDateTime.of(2024, 3, 3, 0, 0, 0), List.of(product2)));

LocalDateTime startDateTime = LocalDateTime.of(2023, 10, 31, 0, 0, 0);
LocalDateTime endDateTime = LocalDateTime.of(2024, 10, 31, 0, 0, 0);

// when
List<Order> result = orderRepository.findOrdersBy(startDateTime, endDateTime, INIT);

// then
Assertions.assertThat(result)
.hasSize(3)
.extracting("registredDateTime", "totalPrice", "orderStatus")
.containsExactlyInAnyOrder(
tuple(LocalDateTime.of(2023, 11, 1, 0, 0, 0), 11000, INIT),
tuple(LocalDateTime.of(2023, 12, 1, 0, 0, 0), 9000, INIT),
tuple(LocalDateTime.of(2024, 3, 3, 0, 0, 0), 5000, INIT)
);
}

private Product createProduct(String productNumber, ProductType type, String productName, ProductSellingStatus productSellingStatus, int price) {
return Product.builder()
.productNumber(productNumber)
.type(type)
.sellingStatus(productSellingStatus)
.name(productName)
.price(price)
.build();
}

}
Loading

0 comments on commit d353416

Please sign in to comment.