Test는 class에 내용들을 작은 단위로 쪼개서 대역을 나누면서 한다고 보면 된다.
생각보다 익히기 어려운 부분이고 의식적으로 노력해야 한다고 생각한다.
이번엔 Todolist 프로젝트 그중 todolist_backend 소스를 가지고 TestCode를 만들어 보았다.
그동안 버그를 확인하기 위해 sysout 또는 log로 위치를 확인하느라 굉장히 번거로웠다.
JUnit을 사용하여 단위테스트를 진행하니, 어디에서 오류가 았나는지 + 최적화 코드를 유추하기 훨씬 편하게 코드를 확인할 수 있다. 그리고
가장 중요한 점은 코드를 수정하는 데 테스트코드가 있으니 변화에 두려워지지 않는다!(CleanCode)
리팩토링 관점에서 수정에 두려움이 없어지는 데 Testcode는 이제 필수다.
이 블로그를 참조하기를 바란다.
https://velog.io/@mooh2jj/Junit-기본-예제
슬라이스 테스트(slice test)
란?
레이어를 독립적으로 테스트하기 위해 Mockito
라이브러리 또는 @DataJpaTest
를 활용했는데, 코드 리뷰를 받으면서 슬라이스 테스트라는 용어를 알게 되었다.
말 그대로 레이어별로 잘라서, 레이어를 하나의 단위로 보는 단위 테스트
를 한다는 것이다.
그럼 Mockito
라이브러리를 활용할 수 있는 Repository
, Controller
, Service
가 슬라이스 테스트라 할 수 있겠다.
build.gradle
// 기본 장착
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// h2 : test db용
runtimeOnly 'com.h2database:h2'
// test에서 lombok 사용 -> @Sl4j 사용가능
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// test sql 확인용
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0'
☑️ 보통, profile test 구조에서 DB는 H2
를 사용한다.
✅ TestCode에 사용할려면 application 파일에 구성에 따라, 기본적으로 Class 파일에 @ActiveProfile("test")
또는 @Profile("test")
을 붙여야 한다.
1) Profile
spring:
profiles: local
---
spring:
profiles: dev
---
spring:
profiles: prod
...
이런 구조의 설정이면, @Profile("test")
구성으로 해놓으면 된다.
2) ActiveProfile
예시)
application.yml (test폴더> resources폴더 or main폴더> resources폴더)
---
spring:
config:
activate:
on-profile: test
jpa:
database: h2
이와 같은 application 프로퍼티 파일 구성이면, @ActiveProfile("test")
하면 된다.
💡 참고) test폴더> resources 폴더에 넣으면, application 프로퍼티 파일 없어도 testCode가 알아서 application "test" 파일을 잡는다.
@Slfj4
@ActiveProfiles("test")
@Import(JpaConfig.class) // Config파일이 필요하다면 import
그리고
build.gradle runtimeOnly 'com.h2database:h2'
추가
여기서, H2 데이터베이스 를 추가 설치 x
test 디렉토리에서 application 파일에서 설정된 것이 없으면, 또는 아예 존재하지 않으면 스프링에서는 자동으로임베디드 DB(h2:mem)
를 설정해준다.
굳이 build.gradle에 runtimeOnly 'com.h2database:h2'
도 할 필요도 없다.
✅참고)
application.yml을 main>resources에서도 설정가능하게 할려면?
(local
, dev
, prod
까지 설정이 가능)
그리고 ---
로 분리할 수 있다.
spring:
profiles: dev
---
spring:
profiles: test
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb;MODE=MySQL;
username: sa
password:
---
spring:
profiles: prod
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:8080/db_name?serverTimezone=UTC&characterEncoding=UTF-8
username: sa
password:
✅ build 시, ./gradlew clean build
할시, testCode를 기본적으로 하게 되는데, @ActiveProfile("test")
또는 @Profile("test")
구성으로 할시 잘된다!
✅ 향후 jar 파일을 배포할 시 옵션 Arguments에 VM arguments로 구동시 변수 설정으로,
-Dspring.profiles.active=local
와 같이 추가할 수 있다.
Repository
,Service
,Controller
코드에 적용되는 TestCode가 다 다르다.
다음 각각 구현 방식을 살펴보자.
보통, given → when → then
에 맞춰 테스트 코드를 작성하는 게 가독성과 명확성 측면에서 좋다.
given
: 테스트에 대한 pre-confitionwhen
: 테스트하고 싶은 동작 호출then
: 테스트 결과 확인then 메서드로는 assertJ의 assertThat
을 많이쓴다. 그리고 Mockito의 verify
를 사용할 수 있다.
✅ Junit에서 의존성 주입은 @Autowired로 한다.
Junit에서는 DI를 지원하는 타입으로 @Autowired로 정해져 있다고 보면 된다.
생성자나 기타 다른 DI 방식 예를 들어,@RequiredArgConstructor
같은 어노테이션은 쓰지 못한다. TestCode에서 작성시 JUnit이 먼저 개입이 되다보니 에러가 나기 때문이다.
// 기본 TestCode 예제
public class HelloResponseDtoTest {
@Test
public void lombokFunctionTest() {
//given
String name = "test";
int amount = 1000;
//when
HelloResponseDto dto = new HelloResponseDto(name, amount);
//then
assertThat(dto.getName()).isEqualTo(name);
assertThat(dto.getAmount()).isEqualTo(amount);
}
}
@SpringBootTest // 스프링 컨테이너와 테스트를 함께 실행한다, 통합테스트용
// @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) 통합테스트 환경을 Mock화
// @Transactional //테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고,
// 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지않는다.
// @DataJpaTest // 오직 JPA 컴포넌트들만 로드함, insert쿼리 안날림, @Transactional(자동Rollback)가 기본적으로 설정됨. 설정해놓은 DB가 아닌 in-memory DB를 활용해서 테스트가 실행됨.(h2 디팬더시 필요)
// @Commit or @Rollback(value = false) // 롤백을 막아줌, 스터디시 테이블에 데이터가 들어가는 것을 확인하기 위해 설정
@ActiveProfiles("test")
// application-test.yml이 동작될때, 실행됨
@Profile("test")
// 무조건 application-test.yml이 동작된다!
// 만약, Test 코드 외에 다른 코드에 이 어노테이션을 사용하면 무조건 application-test.yml이 실행된다는 말이다!
// 1.@BeforeAll 2.@BeforeEach 3.@AfterEach 4.@AfterAll
@BeforeAll // 이 가장 먼저 실행
// ----- 반복
@BeforeEach // 가 실행
@Test // 붙인 메서드 실행
@AfterEach // 실행
// ------ 반복
@AfterAll // 가장 마지막 실행
통합테스트 예시는 이블로그에서 참조해주시면 된다.
https://velog.io/@mooh2jj/Todolist-통합TestCode-만들기
❗
public
public 메서드만 스프링 트랜잭션 AOP @Transactional 기능이 적용된다!
// test 코드 작성할 때 주의!
참고로 public 이 아닌곳에 @Transactional이 붙어 있으면 예외가 발생하지 않고, 트랜잭션 적용만 무시된다.
build.gradle
// RepositoryTest h2데이터에서 설정위함
runtimeOnly 'com.h2database:h2'
@Slf4j
@DataJpaTest
class TodoRepositoryTest {
@Autowired
TodoRepository todoRepository;
TodoEntity todoEntity;
@BeforeEach
public void setup() {
todoEntity = TodoEntity.builder()
.title("testTodo")
.order(0L)
.completed(false)
.build();
}
/* @AfterEach
void afterEach() {
todoRepository.deleteAll(); // rollback을 해주기에 필요x
}*/
@DisplayName("save 테스트")
@Test
public void save(){
// given - precondition or setup
// when - action or the behaviour that we are going test
TodoEntity savedTodo = todoRepository.save(todoEntity);
// then - verify the output
assertThat(savedTodo).isNotNull();
assertThat(savedTodo.getId()).isGreaterThan(0);
assertThat(savedTodo.getTitle()).isEqualTo("testTodo");
}
@DisplayName("add 20개 등록 테스트")
@Test
public void addTest() {
IntStream.rangeClosed(1,20).forEach(i -> {
TodoEntity todoEntity = TodoEntity.builder()
.title("todo_dsg" + i)
.order((long) i)
.completed(true)
.build();
todoRepository.save(todoEntity);
});
}
@DisplayName("getAll 테스트")
@Test
public void getAll() {
TodoEntity todoEntity1 = TodoEntity.builder()
.title("testTodo1")
.order(1L)
.completed(false)
.build();
todoRepository.save(todoEntity);
todoRepository.save(todoEntity1);
List<TodoEntity> all = todoRepository.findAll();
log.info("todoEntity(all): {}", all);
assertThat(all).isNotNull();
assertThat(all.size()).isEqualTo(2); // DB H2일시 true, 실제 DB이면 오류 날 수 있어!
}
@DisplayName("getById 테스트")
@Test
public void getById() {
todoRepository.save(todoEntity);
TodoEntity todoEntity = todoRepository.getById(1L);
log.info("todoEntity(getOne): {}", todoEntity);
assertThat(todoEntity).isNotNull();
}
@DisplayName("update 테스트")
@Test
public void update() {
TodoEntity todoEntity = TodoEntity.builder()
.id(19L)
.title("updatedTitle")
.order(19L)
.completed(true)
.build();
log.info("todoEntity: {}", todoRepository.save(todoEntity));
assertThat(todoEntity.getTitle()).isEqualTo("updatedTitle");
}
@DisplayName("delete 테스트")
@Test
public void deleteById(){
// given - precondition or setup
TodoEntity savedTodoEntity = todoRepository.save(this.todoEntity);
// when - action or the behaviour that we are going test
// todoRepository.deleteById(savedTodoEntity.getId()); // 오류가 난다면 아래로!
todoRepository.delete(savedTodoEntity);
Optional<TodoEntity> deletedTodo = todoRepository.findById(savedTodoEntity.getId());
// then - verify the output
assertThat(deletedTodo).isEmpty();
}
// Page
@Test
public void testPage() {
// Pageable pageable = PageRequest.of(0, 10);
Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
Page<TodoEntity> result = todoRepository.findAll(pageable);
log.info("result: {}", result);
}
@DisplayName("정렬하기 테스트")
@Test
public void sortTest() {
// var todoEntities = todoRepository.findAll(Sort.by(Sort.Direction.DESC, "order"));
// todoEntities.forEach(System.out::println);
Sort sort = Sort.by("id").descending();
Pageable pageable = PageRequest.of(0, 10, sort);
Page<TodoEntity> result = todoRepository.findAll(pageable);
result.get().forEach(System.out::println);
}
@DisplayName("1개 id로 1row 찾기 테스트")
@Test
public void byIdTest() {
var todoEntity = todoRepository.findById(10L);
log.info("todoEntity: {}", todoEntity.orElse(null));
}
@DisplayName("Arrays.asList로 list 찾기 테스트")
@Test
public void listTest() {
List<TodoEntity> allById = todoRepository.findAllById(Arrays.asList(1L, 2L, 3L));
allById.forEach(System.out::println);
}
}
service부터는 Mockito
라이브러리를 사용해 Mocking 테스트가 이루어진다. Mock
이란 가짜란 뜻이다. 주로 DI 개념의 인터페이스를 가져올 때 가짜 객체로 주입할 때 사용한다.
Mock 테스트 순서
1) CreateMock : 인터페이스에 해당하는 Mock 객체를 만든다.
2) Stub : 테스트에 필요한 Mock 객체의 동작을 지정한다. (필요시만)
3) Exercise : 테스트 메스드 내에서 Mock 객체를 사용한다.
4) Verify : 메서드가 예상대로 호출됐는지 검증한다.
그냥given
-when
-then
으로 생각하면 된다!
// serviceTest 주요 코드 예시
@ExtendWith(MockitoExtension.class)
// mock 객체를 생성한다.
@Mock
private TodoRepository todoRepository;
// @Mock이 붙은 목객체를
// @InjectMocks이 붙은 객체에 주입시킬 수 있다
@InjectMocks
private TodoService todoService;
// given-when-then 구조
@Test
void test(){
// given
given(todoRepository.findAll())
.willReturn(List.of(todo1, todo2));
// when
List<TodoEntity> todos = todoService.searchAll();
// then
assertThat(todos.size()).isEqualTo(2);
}
** given(memberService.list()).willReturn(members);
가짜객체가 원하는 행위를 할 수 있도록 정의해준다.(given, when 등을 사용) => BDDMockito의 given()을 많이 사용함
💥아래 error 문구가 나온다면 when() 구문
을 사용해 볼것!
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
memberService의 list() 메서드를 실행시키면 members를 리턴해달라는 요청이다.
** lenient
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
lenient :
불필요한 스터빙을 하지 않도록 되어있는데, 현재 코드에 쓰이지 않는 스터빙을 해놨기 때문에 저런 메시지가 보이는 것이고, lenient는 그런 제약을 느슨하게 허용하는 것이다.
lenient().given(memberService.list()).willReturn(members);
2) assertThat(savedMember).isNotNull();
, verify(memberService).insert(member);
해당 메서드가 실행됐는지를 검증해준다.
✅ when(), verify(), assertThat()을 쓰기 위해, 다음 라이브러리 import가 꼭 필요!
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
// Mockito : Mock을 지원하는 자바 테스트 프레임워크
@Slf4j
@ExtendWith(MockitoExtension.class)
public class TodoServiceTest {
@InjectMocks
private TodoServiceImpl todoService;
// Impl로! mockito @InjectMock 는 구현체를 주입!
@Mock
private TodoRepository todoRepository;
private TodoEntity todoEntity;
@BeforeEach
public void setup() {
todoEntity = TodoEntity.builder()
.id(1L)
.title("testTodo")
.order(0L)
.completed(false)
.build();
}
@Test
public void add() {
when(this.todoRepository.save(any(TodoEntity.class)))
.then(AdditionalAnswers.returnsFirstArg());
// given(todoRepository.save(todoEntity)).willReturn(todoEntity);
TodoRequest todoRequest = TodoRequest.builder()
.title(todoEntity.getTitle())
.order(todoEntity.getOrder())
.completed(todoEntity.getCompleted())
.build();
TodoResponse savedResponse = this.todoService.add(todoRequest);
log.info("savedResponse: {}", savedResponse);
// assertEquals(1L, savedResponse.getId()); // test 오류 null 뜨는지 이해 불가.
// assertEquals("testTodo", savedResponse.getTitle());
assertThat(savedResponse).isNotNull();
assertThat(savedResponse.getTitle()).isEqualTo(todoRequest.getTitle());
}
@Test
public void searchById() {
Optional<TodoEntity> expected = Optional.of(todoEntity);
given(this.todoRepository.findById(1L))
.willReturn(expected);
TodoResponse response = this.todoService.searchById(1L);
log.info("response: {}", response);
// assertEquals(response.getOrder(), 0L);
// assertFalse(response.getCompleted());
// assertEquals(response.getTitle(), "testTodo");
assertThat(response.getTitle()).isEqualTo("testTodo");
assertThat(response.getOrder()).isEqualTo(0L);
}
// 에러 발생 테스트도 만듦
@Test
public void searchById_ThrowsException() {
given(this.todoRepository.findById(anyLong())).willReturn(Optional.empty());
// assertThrows(ResponseStatusException.class, () -> {
// this.todoService.searchById(1L);
// });
assertThatThrownBy(() -> {
todoService.searchById(1L);
}).isInstanceOf(ResponseStatusException.class);
}
// 에러 메시지 확인 테스트
@Test
@DisplayName("Member 중복 테스트")
public void saveDuplicateMemberTest() {
memberRepository.save(member);
assertThatThrownBy(() -> {
memberService.saveMember(member);
}).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("이미 가입된 회원입니다.");
}
@Test
public void searchAll(){
// given - precondition or setup
TodoEntity todoEntity1 = TodoEntity.builder()
.id(100L)
.title("test_dsg")
.order(1L)
.completed(true)
.build();
given(todoRepository.findAll()).willReturn(List.of(todoEntity, todoEntity1));
// when - action or the behaviour that we are going test
List<TodoResponse> todoResponses = todoService.searchAll();
log.info("todoResponses: {}", todoResponses);
// then - verify the output
assertThat(todoResponses).isNotNull();
assertThat(todoResponses.size()).isEqualTo(2);
}
@Test
public void searchAll_negative(){
// given - precondition or setup
given(todoRepository.findAll()).willReturn(Collections.emptyList());
// when - action or the behaviour that we are going test
List<TodoResponse> todoResponses = todoService.searchAll();
log.info("todoResponses: {}", todoResponses);
// then - verify the output
assertThat(todoResponses).isEmpty();
assertThat(todoResponses.size()).isEqualTo(0);
}
@Test
public void updateById(){
// given - precondition or setup
given(todoRepository.findById(anyLong()))
.willReturn(Optional.of(todoEntity));
given(todoRepository.save(todoEntity)).willReturn(todoEntity); // updateById면 두 상황 모두 만들어져야돼!
TodoRequest request = TodoRequest.builder()
.title("kkk")
.order(2L)
.build();
// when - action or the behaviour that we are going test
TodoResponse todoResponse = todoService.updateById(todoEntity.getId(), request);
log.info("todoResponses: {}", todoResponse);
// then - verify the output
assertThat(todoResponse.getTitle()).isEqualTo(request.getTitle());
assertThat(todoResponse.getOrder()).isEqualTo(request.getOrder());
}
@Test
public void deleteById(){
// given - precondition or setup
Long todoId = 1L;
willDoNothing().given(todoRepository).deleteById(todoId);
// 안될시
// given(todoRepository.findById(anyLong())).willReturn(Optional.of(todoEntity));
// when - action or the behaviour that we are going test
todoService.deleteById(todoId);
// then - verify the output
verify(todoRepository).deleteById(todoId);
}
}
주로 MockMvc mvc객체로 검증한다.
// Controller 레이어 기본 세팅
// 특정레이어만 test (vs @SpringBootTest: 전체)
// @Controller @RestController @ControllerAdvice 등등 컨트롤러와 연관된 bean들이 로드됨
@WebMvcTest(TodoController.class)
// MockMvc: api 테스트용으로 시뮬레이션하여 MVC가 되도록 도와주는 클래스
@Autowired
private MockMvc mvc;
// @MockBean: ApplicationContext에 mock객체를 추가
@MockBean
private TodoService todoService;
contentType(MediaType.APPLICATION_JSON)
ObjectMapper
선언, String content = mapper.writeValueAsString(request);
: 객체 -> String으로 직렬화
ObjectMapper
readValue()
// 참고로 mapper.readValue()는 역직렬화(String -> Object)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// do various things, perhaps:
String someJsonString = mapper.writeValueAsString(someClassInstance);
SomeClass someClassInstance = mapper.readValue(someJsonString, SomeClass.class)
mvc.perform(get("/hello"))
: MockMvc를 통해 /hello 주소로 HTTP GET 요청
.andExpect(status().isOk())
: mvc.perform의 결과를 검증
.param
: API테스트할 때 사용될 요청 파라미터
를 설정한다.
.andExpected(jsonPath("$.name", is(name)))
: JSON 응답값을 필드별로 검증할 수 있는 메소드, $를 기준으로 필드명
을 명시한다.
.andDo(print())
: 요청,응답에 대해서 콘솔창에 print해줌!
관련된 메서드들은 다음 MockMvcRequestBuilders, MockMvcResultMatchers 객체가 필요하다, 기억해두면 좋다!
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(TodoController.class)
public class TodoControllerTest {
@Autowired
MockMvc mvc;
@MockBean
private TodoServiceImpl todoService;
@Autowired
ObjectMapper mapper;
private TodoEntity expected;
@BeforeEach
void setup() {
this.expected = new TodoEntity();
this.expected.setId(123L);
this.expected.setTitle("test");
this.expected.setOrder(0L);
this.expected.setCompleted(false);
}
@Test
void create() throws Exception {
// when(this.todoService.add(any(TodoRequest.class)))
// .then((i) -> {
// TodoRequest request = i.getArgument(0, TodoRequest.class);
// return new TodoResponse(this.expected.getId(), request.getTitle(), request.getOrder(), request.getCompleted());
// });
TodoRequest todoRequest = TodoRequest.builder()
.title(expected.getTitle())
.order(expected.getOrder())
.completed(expected.getCompleted())
.build();
TodoResponse todoResponse = TodoResponse.builder()
.id(expected.getId())
.title(todoRequest.getTitle())
.order(todoRequest.getOrder())
.completed(todoRequest.getCompleted())
.build();
given(todoService.add(todoRequest)).willReturn(todoResponse);
String content = mapper.writeValueAsString(request);
// MockMvcRequestBuilders 객체 사용
mvc.perform(post("/todo")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(expected.getId()))
.andExpect(jsonPath("$.title").value(expected.getTitle()))
.andExpect(jsonPath("$.order").value(expected.getOrder()))
.andExpect(jsonPath("$.completed").value(expected.getCompleted()))
.andDo(print());
verify(todoService).add(todoRequest);
}
@Test
void readOne() throws Exception {
TodoResponse todoResponse = TodoResponse.builder()
.id(expected.getId())
.title(expected.getTitle())
.order(expected.getOrder())
.completed(expected.getCompleted())
.build();
given(todoService.searchById(123L)).willReturn(todoResponse);
mvc.perform(get("/todo/{id}", 123L))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(expected.getId()))
.andExpect(jsonPath("$.title").value(expected.getTitle()))
.andExpect(jsonPath("$.order").value(expected.getOrder()))
.andExpect(jsonPath("$.completed").value(expected.getCompleted()));
}
@Test
void readOneException() throws Exception {
given(todoService.searchById(123L)).willThrow(new ResponseStatusException(HttpStatus.NOT_FOUND));
mvc.perform(get("/todo/{id}", 123L))
.andExpect(status().isNotFound());
}
@Test
void readAll() throws Exception {
int expectedLength = 10;
List<TodoResponse> mockList = new ArrayList<>();
// for (int i = 0; i < expectedLength; i++) {
// mockList.add(todoResponse);
// }
IntStream.rangeClosed(1,expectedLength).forEach(i -> {
TodoResponse todoResponse = TodoResponse.builder()
.title(expected.getTitle()+" "+i)
.order(expected.getOrder())
.completed(expected.getCompleted())
.build();
mockList.add(todoResponse);
});
given(todoService.searchAll()).willReturn(mockList);
mvc.perform(get("/todo"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(expectedLength));
}
@Test
public void updateById() throws Exception {
Long todoId = 123L;
TodoRequest todoRequest = TodoRequest.builder()
.title("new_title")
.order(1L)
.completed(true)
.build();
TodoResponse todoResponse = TodoResponse.builder()
.id(todoId)
.title(todoRequest.getTitle())
.order(todoRequest.getOrder())
.completed(todoRequest.getCompleted())
.build();
// given(todoService.updateById(todoId)) // 오류남
// .willAnswer((v) -> v.getArgument(0));
given(todoService.updateById(todoId)).willReturn(todoResponse);
mvc.perform(put("/todo/{id}", todoId)
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.title", CoreMatchers.is(todoResponse.getTitle())))
.andExpect(jsonPath("$.order", CoreMatchers.is(1))) // 1L이라 오류날수 있어 value 1 넣음
.andExpect(jsonPath("$.completed", CoreMatchers.is(todoResponse.getCompleted())));
}
@Test
public void deleteById() throws Exception {
Long todoId = 123L;
willDoNothing().given(todoService).deleteById(todoId);
mvc.perform(delete("/todo/{id}", todoId))
.andDo(print())
.andExpect(status().isOk());
}
@Test
void deleteAll() throws Exception {
mvc.perform(delete("/todo"))
.andExpect(status().isOk());
}
}
// @SpringBootTest 필요 x, application-test.yml 필요 x
// @Transactional 필요 x
@Slf4j
class CouponRecordTest extends DummyObject {
@Test
@DisplayName("사용하지 않은 쿠폰, 사용 상태로 변경")
void changeCouponStatusToUsed() {
// Given
CouponRecord couponRecord = createCouponRecord();
log.info("couponRecord: {}", couponRecord);
// When
couponRecord.changeCouponStatusToUsed();
// Then
assertThat(couponRecord.getStatus()).isEqualTo(CouponRecordStatus.USED);
assertThat(couponRecord.getUsedDatetime()).isNotNull();
}
@Test
@DisplayName("만료된 쿠폰, 만료 상태로 변경")
void changeExpiredCouponStatus() {
// Given
CouponRecord couponRecord = createCouponRecord();
log.info("couponRecord: {}", couponRecord);
// When
couponRecord.changeExpiredCouponStatus();
// Then
assertThat(couponRecord.getStatus()).isEqualTo(CouponRecordStatus.EXPIRED);
}
}
contextLoads() FALIED error
운영에서 테스트 중 위와 같은 에러로 빌드가 계속 실패하였는데
프로젝트명 ApplicationTests.java 파일의@SpringBootTest
어노테이션을 주석처리하거나test 폴더 하위에 *.yml 파일을 두시면 해결 될 것
입니다.