- Exception의 종류
- Checked Exception
- Unchecked Exception (Runtime Exception)
- Custom Exception의 필요성
- 시스템의 오류와 분류하여 처리하기 위함
- 구체화된 테스트를 만들기 용이함
@Slf4j
public class PersonNotFoundException extends RuntimeException {
private static final String MESSAGE = "Person Entity가 존재하지 않습니다";
public PersonNotFoundException() {
super(MESSAGE);
log.error(MESSAGE);
}
}
@Slf4j
public class RenameIsNotPermittedException extends RuntimeException {
private static final String MESSAGE = "이름 변경이 허용되지 않습니다";
public RenameIsNotPermittedException() {
super(MESSAGE);
log.error(MESSAGE);
}
}
@Service
@Slf4j
public class PersonService {
@Autowired
private PersonRepository personRepository;
public List<Person> getPeopleByName(String name) {
return personRepository.findByName(name);
}
@Transactional(readOnly = true)
public Person getPerson(Long id) {
return personRepository.findById(id).orElse(null);
}
@Transactional
public void put(PersonDto personDto) {
Person person = new Person();
person.set(personDto);
person.setName(personDto.getName());
personRepository.save(person);
}
@Transactional
public void modify(Long id, PersonDto personDto) {
Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new);
if (!person.getName().equals(personDto.getName())) {
throw new RenameIsNotPermittedException();
}
person.set(personDto);
personRepository.save(person);
}
@Transactional
public void modify(Long id, String name) {
Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new);
person.setName(name);
personRepository.save(person);
}
@Transactional
public void delete(Long id) {
Person person = personRepository.findById(id).orElseThrow(PersonNotFoundException::new);
person.setDeleted(true);
personRepository.save(person);
}
}
@ExtendWith(MockitoExtension.class)
class PersonServiceTest {
@InjectMocks
private PersonService personService;
@Mock
private PersonRepository personRepository;
@Test
void getPeopleByName() {
when(personRepository.findByName("martin"))
.thenReturn(Lists.newArrayList(new Person("martin")));
List<Person> result = personService.getPeopleByName("martin");
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0).getName()).isEqualTo("martin");
}
@Test
void getPerson() {
when(personRepository.findById(1L))
.thenReturn(Optional.of(new Person("martin")));
Person person = personService.getPerson(1L);
assertThat(person.getName()).isEqualTo("martin");
}
@Test
void getPersonIfNotFound() {
when(personRepository.findById(1L))
.thenReturn(Optional.empty());
Person person = personService.getPerson(1L);
assertThat(person).isNull();
}
@Test
void put() {
personService.put(mockPersonDto());
verify(personRepository, times(1)).save(argThat(new IsPersonWillBeInserted()));
}
@Test
void modifyIfPersonNotFound() {
when(personRepository.findById(1L))
.thenReturn(Optional.empty());
assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, mockPersonDto()));
}
@Test
void modifyIfNameIsDifferent() {
when(personRepository.findById((1L)))
.thenReturn(Optional.of(new Person("tony")));
assertThrows(RenameIsNotPermittedException.class, () -> personService.modify(1L, mockPersonDto()));
}
@Test
void modify() {
when(personRepository.findById(1L))
.thenReturn(Optional.of(new Person("martin")));
personService.modify(1L, mockPersonDto());
verify(personRepository, times(1)).save(argThat(new IsPersonWillBeUpdated()));
}
@Test
void modifyByNameIfPersonNotFound() {
when(personRepository.findById(1L))
.thenReturn(Optional.empty());
assertThrows(PersonNotFoundException.class, () -> personService.modify(1L, "daniel"));
}
@Test
void modifyByName() {
when(personRepository.findById(1L))
.thenReturn(Optional.of(new Person("martin")));
personService.modify(1L, "daniel");
verify(personRepository, times(1)).save(argThat(new IsNameWillBeUpdated()));
}
@Test
void deleteIfPersonNotFound() {
when(personRepository.findById(1L))
.thenReturn(Optional.empty());
assertThrows(PersonNotFoundException.class, () -> personService.delete(1L));
}
@Test
void delete() {
when(personRepository.findById(1L))
.thenReturn(Optional.of(new Person("martin")));
personService.delete(1L);
verify(personRepository, times(1)).save(argThat(new IsPersonWillBeDeleted()));
}
private PersonDto mockPersonDto() {
return PersonDto.of("martin", "programming", "판교", LocalDate.now(), "programmer", "010-1111-2222");
}
private static class IsPersonWillBeInserted implements ArgumentMatcher<Person> {
@Override
public boolean matches(Person person) {
return equals(person.getName(), "martin")
&& equals(person.getHobby(), "programming")
&& equals(person.getAddress(), "판교")
&& equals(person.getBirthday(), Birthday.of(LocalDate.now()))
&& equals(person.getJob(), "programmer")
&& equals(person.getPhoneNumber(), "010-1111-2222");
}
private boolean equals(Object actual, Object expected) {
return expected.equals(actual);
}
}
private static class IsPersonWillBeUpdated implements ArgumentMatcher<Person> {
@Override
public boolean matches(Person person) {
return equals(person.getName(), "martin")
&& equals(person.getHobby(), "programming")
&& equals(person.getAddress(), "판교")
&& equals(person.getBirthday(), Birthday.of(LocalDate.now()))
&& equals(person.getJob(), "programmer")
&& equals(person.getPhoneNumber(), "010-1111-2222");
}
private boolean equals(Object actual, Object expected) {
return expected.equals(actual);
}
}
private static class IsNameWillBeUpdated implements ArgumentMatcher<Person> {
@Override
public boolean matches(Person person) {
return person.getName().equals("daniel");
}
}
private static class IsPersonWillBeDeleted implements ArgumentMatcher<Person> {
@Override
public boolean matches(Person person) {
return person.isDeleted();
}
}
}