DB연동 및 mybatis 설정이 끝났다면

junit 을 이용하여 설정이 잘 되었는지 확인을 해보자.

 

1. Test 코드 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.jpp;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.jpp.main.config.MybatisConfig;
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class MainServiceTest {
    
    @Autowired
    private MainDAO mainDao;
    
    @Test
    public void getUserList() throws Exception {
        
        for(UserVO vo : mainDao.getUserList()) {
            System.out.println(vo.getName());
        }
        
    }
}
 
cs

Spring Framework 에서 Bean 을 가져오기위해 @ContextConfiguration(classes = ~) 을 사용했지만 

Spring Boot 는 @*Test(@SpringBootTest 를 포함한 각종 Test 어노테이션) 사용시 Bean 을 알아서 가져온다고 한다.

참고 및 부분 발췌

※JUnit 4 사용시 @RunWith(SpringRunner.class) 를 꼭 달아주어야 한다.

※ @SpringBootTest(classes = {MainDAO.class, MybatisConfig.class}) 와 같이 테스트에 사용할 특정 Bean 을 지정해 줄 수 도 있다.

※ ApplicationContext (스프링컨텍스트) 에서 DAO bean 을 .getBean으로 가져와 테스트 하는 것 또한 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.jpp;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.jpp.main.config.MybatisConfig;
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MainDAO.class, MybatisConfig.class})
public class MainServiceTest {
    
    //@Autowired
    //private MainDAO mainDao;
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void getUserList() throws Exception {
        
        MainDAO dao = context.getBean("mainDAO", MainDAO.class);
        
        for(UserVO vo : dao.getUserList()) {
            System.out.println(vo.getName());
        }
    }
}
 
cs

 

2. Run as JUnit Test 로 실행.

결과 :

 

콘솔 결과 :

 

만약 테스트를 기존에 사용하는 DB가 아닌 별도의 DB 에서 하고자 하는 경우는 어떻게 해야할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.jpp;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class MainServiceTest {
    
    @Autowired
    private MainDAO mainDao;
    
//    @Autowired
//    private ApplicationContext context;
    
    @Test
    public void getUserList() throws Exception {
        
        //MainDAO dao = context.getBean("mainDAO", MainDAO.class);
        
        for(UserVO vo : mainDao.getUserList()) {
            System.out.println(vo.getName());
        }
    }
    
    @TestConfiguration
    @MapperScan(value= {"com.jpp.main.mapper"})
    @EnableTransactionManagement
    public static class TestConfig {
 
            @Bean
            public DataSource customDataSource() {
                return DataSourceBuilder.create()
                                        .url("jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC")
                                        .driverClassName("com.mysql.cj.jdbc.Driver")
                                        .username("id")
                                        .password("pw")
                                        .build();
            }
            
            @Bean
            public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
                    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
                    sessionFactory.setDataSource(dataSource);
                    
                    Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
                    
                    sessionFactory.setMapperLocations(res);
                    
                    return sessionFactory.getObject();
            }
 
            @Bean
            public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
                return new SqlSessionTemplate(sqlSessionFactory);
            }
    }
}
 
cs

위와 같이 @TestConfiguration 을 테스트 코드 내부에 inner Class로 작성하여 테스트만을 위한 DB 설정을 따로 해줄 수 있다.

테스트 코드 내부에 inner Class 로 @TestConfiguration 작성시 별다른 설정이 없어도 @SpringBootTest 가 해당 config Bean을 인식하여 DI 한다.

inner Class가 아닌 외부에 TestConfig.java 파일을 따로 작성해줄 경우 @Import(TestConfig.class) 어노테이션을 @SpringBootTest 와 함께 달아주어야 테스트 코드에서 주입 및 사용이 가능하다.

ex)

@RunWith(SpringRunner.class)
@SpringBootTest
@Import(TestConfig.class)

 

그럼 이제 위의 테스트 코드를 실행해 보자.

아래와 같은 에러가 발생한다.

 

The bean 'customDataSource', defined in com.jpp.MainServiceTest$TestConfig, could not be registered. A bean with that name has already been defined in class path resource [com/jpp/main/config/MybatisConfig.class] and overriding is disabled.

 

동일한 이름을 가진 Bean이 MybatisConfig.class에 이미 정의되어 있어 TestConfiguration 으로 작성한 Bean 을 등록할 수 없단다.

 

※ 동일한 Bean 의 이름이 존재할 경우 IoC 컨테이너가 Test 코드 내의 TestConfiguration 어노테이션이 달린 Bean 을 우선적으로 인식하여 주입해줄까 싶었는데 그것 까진 안되나보다.

27번라인을 아래와 같이 고쳐주자

@SpringBootTest(classes = {TestConfig.class, MainDAO.class})

: @SpringBootTest 어노테이션에 테스트 DB가 설정되어있는 Bean class와 이를 사용할 class 를 명시

 

다시 테스트 모드로 실행시켜보면 결과는 성공일 것이다.

 

 

※ Test용 Config 를 외부 클래스로 작성하고 테스트코드에서 사용하고자 할 경우

1. 앞서 언급한 @Import 를 사용하는 대신

2. @SpringBootTest(classes = {TestConfig.class}) 와 같이 테스트용 Config 를 명시하여 사용 할 수 도 있다.

   (@Import 를 사용하지 않아도 주입 받아 사용이 가능)

 

※ 단, 위처럼 프로젝트 내에 동일한 이름의 Bean 이 존재할 경우(2개의 DataSource Bean 이 동일한 이름으로 존재) @SpringBootTest(classes = {TestConfig.class, MainDAO.class})와 같이 테스트에 사용할 Bean을 명시하여 사용해야한다. (@Import만 사용시 앞서 살펴보았던 에러와 동일하게, Bean 이 중복 선언된다는 Exception이 발생한다)

 

 

* Controller를 테스트 하는 경우 아래와 같이 가능하다.

1. 테스트 대상 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.jpp.main.controller;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
 
import com.jpp.main.service.MainService;
import com.jpp.main.vo.UserVO;
 
@Controller
public class MainController {
    
    @Autowired
    private MainService mainService;
    
    @PostMapping("/api2")
    @ResponseBody
    public String api(@RequestBody String test) throws Exception {
        String rs = "good";
        
        System.out.println("api2 called !");
        
        return "{\"good\":\"res\"}";
    }
}
 
cs

 

 

2. test class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.jpp;
 
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
 
import com.jpp.main.controller.MainController;
import com.jpp.main.service.MainService;
 
@RunWith(SpringRunner.class)
@WebMvcTest(MainController.class)
public class MainServiceTest2 {
    
    @Autowired
    private MockMvc mvc;
    
    @MockBean
    private MainService mainService;
    
    @Test
    public void api() throws Exception {
        
        
        ResultActions actions = 
                mvc.perform(post("/api2")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content("{\"test\":\"val\"}")
                        ).andDo(print());
        
        actions.andExpect(status().isOk());
    }
    
}
 
cs

 

3. junit 실행 결과

 

 

JUnit 과 관련된 트랜잭션(@Transactional) 부분, Mock 객체 등에 대한 내용은 추가 공부 및 정리 후 작성 하겠다.

 

 

Spring Boot의 Test코드에 대한 설명이 매우 잘 되어 있는 글

 

반응형

+ Recent posts