→ Spring Container 와 DB를 연결하여 통합 테스트 진행
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
// Container 한테 Service 와 Repository 를 부름
// field 기반으로 injection
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void join() throws Exception{
// given
Member member = new Member();
member.setName("Hello");
// when
Long saveId = memberService.join(member);
// then
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
@Test
void 중복회원방지() throws Exception {
// given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
// when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
Assertions.assertThat(e.getMessage()).isEqualTo("Already Exist.");
// then
}
}
SpringBoot Test
: Container 와 함께 테스트 실행
Transactional
: 테스트 케이스에 @Transactional 어노테이션이 있다면, Transactional 을 먼저 실행해서 모든 테스트 진행 후 데이터 베이스를 롤백 시킴 (DB에 데이터 반영이 안됨)
순수한 단위 테스트가 제일 좋은 테스트라고 할 수 있음
MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거, But SQL은 직접 작성해야 함
Repository/JdbcTemplateMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
private JdbcTemplate jdbcTemplate;
// @Autowired 생성자가 하나일 경우 생략 가능
public JdbcTemplateMemberRepository(DataSource dataSource){
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
// query 사용하지 않고 편리하게 insert 가능
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member; }
@Override
public Optional<Member> findById(Long id) {
// template method pattern
List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(),id);
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(),name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
// 객체 생성
private RowMapper<Member> memberRowMapper(){
// java lambda style
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong( "id"));
member.setName(rs.getString("name"));
return member;
};
}
}