싸피 스프링 과제 중 Junit 테스트를 하는데, 자꾸 NoSuchBeanDefinitionException
라는 에러가 발생했다. 설정은 분명 제대로 했는데 도대체 왜 안되는지 몰라서 한참을 해멨는데, 이 에러에 대한 해답을 알고나니 너무 바보같았다 ㅠ
그림 1) root-context.xml 파일
프로젝트의 root-context.xml 파일이다.
dataSource라는 빈이 있는데, 이 빈은 JndiObjectFactoryBean이라는 클래스를 스프링 컨테이너에 등록한다. 그런데 나는 아무 생각 없이 DataSource라는 이름으로 등록되었다는 이유만으로 DataSource 클래스에 이 빈을 주입시키려는 바보같은 짓을 했다,,,,
그림 2) 테스트 코드
위의 코드가 테스트 코드이다. DataSource는 빈으로 등록되어있지 않았는데, @Autowired
로 등록하려고 해서 벌어진 예외 상황이었다. 원래 쓰려고 했던 클래스는 SqlSession이었으므로 DataSource 클래스 대신 SqlSession으로 변경하니 이 예외는 사라졌다.
하지만 저거를 고치고 나니 새로운 에러가 터져버렸다...
이 예외는 JNDI가 초기화에 실패해서 발생하는 예외이다. 오류 로그는 다음과 같이 발생했다.
Caused by: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or in an application resource file: java.naming.factory.initial
at java.naming/javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:702)
at java.naming/javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305)
at java.naming/javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:342)
at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409)
at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:157)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:92)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:157)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96)
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:114)
at org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:239)
at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:225)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)
이거에 대해서 구글링을 해보니 JNDI는 WAS에 설정 정보가 있고, WAS를 켰을 때 그에 대한 정보를 읽는다고 하는데 JUnit은 WAS 없이 테스트하기 때문에 JNDI가 초기화되지 않아서 발생하는 것 같다. 실제로 WAS를 켜서 실제 서비스에서 DB 연동이 되는지 확인해보니 제대로 작동하는 걸 확인할 수 있었다.
그림 3) 실제 서비스 환경
JUnit 테스트 환경은 WAS와 별개이고 여기에서 JNDI를 사용하기 위해서는 따로 설정을 해줘야 한다는 말이다.
그 설정은 다음과 같다.
package com.ssafy.ws;
import org.junit.runners.model.InitializationError;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jndi.JndiTemplate;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class TestSpringRunner extends SpringJUnit4ClassRunner {
public TestSpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
try {
bindJndi();
} catch (Exception e) {
}
}
@SuppressWarnings("deprecation")
private void bindJndi() throws Exception {
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.activate();
JndiTemplate jndiTemplate = new JndiTemplate();
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/ssafydb?useSSL=false&serverTimezone=UTC");
dataSource.setUsername("ssafy");
dataSource.setPassword("ssafy");
jndiTemplate.bind("java:comp/env/jdbc/ssafy", dataSource);
}
}
SpringJUnit4ClassRunner를 상속받아 TestSpringRunner라는 클래스를 만들어준다. 그리고 bindJndi()
라는 메서드를 생성해서 jndiTemplate
이라는 객체를 만들어서 데이터 소스를 바인딩한다. 그러면 나중에 테스트 환경에서 jndi를 로드하려고 할 때, jndiTemplate에 바인딩되어 있는 java:comp/env/jdbc/ssafy
라는 이름의 데이터 소스가 있기 때문에 WAS가 없어도 스프링 컨테이너에 빈을 등록해서 사용할 수 있게 된다. (참고로 테스트 환경을 아래 사진처럼 위에 만든 TestSpringRunner
클래스로 돌려줘야한다.
그림 4) TestSpringRunner 환경에서 테스트해야한다.
이렇게 하고나니 드디어 정상적으로 테스트를 통과하는 걸 확인할 수 있었다!!!!!!
그림 5) 테스트를 통과하는 모습
JUnit은 항상 스프링 부트 환경에서 간단한 테스트만 해봐서 WAS 없이 스프링 환경에서 단위 테스트를 돌리려고 하니까 제대로 되지 않는 부분이 이렇게 많을 줄은 몰랐다. 제대로 된 단위 테스트를 만들기 위해서 테스트에 대한 공부가 많이 필요하다는 것을 이번 싸피 과제를 통해 알게 되었다.