DB Test[@Transactional, 임베디드 모드 DB]
테스트에서의 @Transactional
테스트를 할때 테스트 후 데이터가 다시 삭제 되어야 할때가 있다.
이럴때 사용할 수 있는게 @Transactional 이다.
원래 @Transactional은 로직이 정상 수행되면 커밋되어 데이터베이스에 변경사항을 저장하지만,
테스트에서 사용될 경우 모든 로직이 트랜잭션 안에서 수행되고 테스트가 끝나면 롤백을 수행하여 테스트에서 생성되거나 변경된 데이터를 롤백시킨다.
package hello.itemservice.domain;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@Transactional
@SpringBootTest
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
/* @Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status;
@BeforeEach
void beforeEach(){
//트랜잭션 시작
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}*/
@AfterEach
void afterEach() {
//MemoryItemRepository 의 경우 제한적으로 사용
if (itemRepository instanceof MemoryItemRepository) {
((MemoryItemRepository) itemRepository).clearStore();
}
//트랜잭션 롤백
//transactionManager.rollback(status);
}
//@Commit //롤백 안하고 커밋하여 DB에 저장됨
//@Transactional
@Test
void save() {
//given
Item item = new Item("itemA", 10000, 10);
//when
Item savedItem = itemRepository.save(item);
//then
Item findItem = itemRepository.findById(item.getId()).get();
assertThat(findItem).isEqualTo(savedItem);
}
@Test
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId).get();
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
}
@Test
void findItems() {
//given
Item item1 = new Item("itemA-1", 10000, 10);
Item item2 = new Item("itemA-2", 20000, 20);
Item item3 = new Item("itemB-1", 30000, 30);
itemRepository.save(item1);
itemRepository.save(item2);
itemRepository.save(item3);
//둘 다 없음 검증
test(null, null, item1, item2, item3);
test("", null, item1, item2, item3);
//itemName 검증
test("itemA", null, item1, item2);
test("temA", null, item1, item2);
test("itemB", null, item3);
//maxPrice 검증
test(null, 10000, item1);
//둘 다 있음 검증
test("itemA", 10000, item1);
}
void test(String itemName, Integer maxPrice, Item... items) {
List<Item> result = itemRepository.findAll(new ItemSearchCond(itemName, maxPrice));
assertThat(result).containsExactly(items);
}
}
※ 만약 테스트에서도 DB에 데이터가 저장되는 것을 원한다면 @Commit을 넣어주면 된다.
임베디드 모드 DB
테스트에 사용되는 DB와 실제 애플리케이션에 사용되는 DB를 분리하여 사용하고 싶다면 임베디드 모드를 사용하면 된다.
임베디드 모드는 애플리케이션이 실행되면 메모리 DB가 생성되고 애플리케이션이 종료되면 임베디드 모드로 동작하는 DB가 함께 종료된다.
즉, 애플리케이션에서 자바 메모리를 함께 사용하는 라이브러리처럼 동작하는 것이다.
※임베디드 모드 직접 사용법
< ItemServiceApplication>
package hello.itemservice;
import hello.itemservice.config.*;
import hello.itemservice.repository.ItemRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
@Slf4j
//@Import(MemoryConfig.class)
//@Import(JdbcTemplateV1Config.class)
//@Import(JdbcTemplateV2Config.class)
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
@Bean
@Profile("local")
public TestDataInit testDataInit(ItemRepository itemRepository) {
return new TestDataInit(itemRepository);
}
@Bean
@Profile("test")
public DataSource dataSource(){
log.info("메모리 데이터베이스 초기화");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
@Profile("test") ==> 프로필이 test인 경우에만 데이터소스를 스프링 빈으로 등록한다.
<test -> resource에 테이블 정의 SQL 추가>
drop table if exists item CASCADE;
create table item
(
id bigint generated by default as identity,
item_name varchar(10),
price integer,
quantity integer,
primary key (id)
);
메모리 DB는 애플리케이션 종료와 함께 테이블도 삭제 되므로 테스트 실행전에 테이블을 생성 해줘야 한다.
<test의 application.properties>
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa
logging.level.org.springframework.jdbc=debug
위 과정과 같이 임베디드 모드를 사용할 수 있지만 스프링 부트가 이러한 과정을 자동화 해준다.
스프링 부트를 사용한 임베디드 모드 사용법
/* @Bean
@Profile("test")
public DataSource dataSource(){
log.info("메모리 데이터베이스 초기화");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}*/
스프링 부트에서는 직접 설정했던 메모리 DB용 데이터소스를 주석처리해도 된다.
spring.profiles.active=test
#spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
#spring.datasource.username=sa
#jdbcTemplate sql log
logging.level.org.springframework.jdbc=debug
spring.datasource.url, spring.datasource.username을 test에서 따로 설정해줄 필요가 없다.
왜냐하면 설정 정보가 따로 없으면 스프링 부트가 임베디드 모드로 접근하는 데이터소스를 자동으로 만들어 제공하므로 따로 설정을 해주지 않아도 임베디드 모드로 접근하게 된다.