테스트 코드를 생성해서 등록된 빈 조회해보기
ApplicationContextInfoTest.java
class ApplicationContextInfoTest { // junit5부터는 public이 없어도 된다.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); //컨테이너에 등록된 빈 이름들 배열을 가져온다.
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);// 받아온 빈의 이름을 가지고 하나씩 꺼내본다, 타입은 지정안했기때문에 object로 꺼내진다.
System.out.println("name = " + beanDefinitionName + " Object : "+bean);
}
}
}
결과
팁
리스트나, 배열과 같은 for문으로 돌려서 내용을 하나씩 살펴보려고할때
iter 치고 탭누르면 자동완성 시켜준다.
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); //컨테이너에 등록된 빈 이름들 배열을 가져온다.
for (String beanDefinitionName : beanDefinitionNames) {
}
string배열 밑에서 iter치고 탭누르면 생성되는 모습
내가 등록한 빈만 보고싶다면
스프링에서 내부적으로 필요해서 등록한 빈이 아닌 , 내가 개발하면서 필요해서 등록한 빈들만 보고 싶다면
다음과 같이 코드를 짜면된다.
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); //컨테이너에 등록된 빈 이름들 배열을 가져온다.
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);// 빈에 대한 메타데이터정보를 가져오는 메소드
//스프링이 필요해서 알아서 등록한 빈들이 아니라 내가 어플리케이션은 개발하기위해 등록한 빈들은 ROLE_APPLICATION이라는 role을 가진다.
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);// 그런 빈들만 가져와서 출력해본다.
System.out.println("name = " + beanDefinitionName + " Object : "+bean);
}
}
}
이렇게 어떤 빈들이 등록됬는지 눈으로 체크가능하다.
ApplicationContextBasicFindTest.java
기본적인 빈 조회 테스트들 생성해본다.
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(
AppConfig.class); //스프링 컨테이너 생성
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService",
MemberService.class); // 이름과 타입으로 빈을 조회하고 가져온다.
// System.out.println("memberService = " + memberService);
// System.out.println("memberService.getClass() = " + memberService.getClass());
assertThat(memberService).isInstanceOf(
MemberServiceImpl.class);// 받아온 멤버서비스가 멤버서비스임플의 객체냐? 라고 물어보는 메소드
//Assertions는 알트엔터로 static화 한다.
}
@Test
@DisplayName("이름 없이 빈 타입으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class); // 이름빼고 타입으로만 조회해서 가져와본다.
assertThat(memberService).isInstanceOf(
MemberServiceImpl.class);// 받아온 멤버서비스가 멤버서비스임플의 객체냐? 라고 물어보는 메소드
}
@Test
@DisplayName("구체 타입으로로 조회")
// 인터페이스가 아닌 구현체 클래스로 검색도 가능하다. 스프링컨테이너에 등록된 객체이므로
void findBeanByName2() { //하지만 이렇게 적는게 좋지않다. -> 구현체를 바꾸면 이 코드 또한 수정이 필요할것이니까, 그러나 가끔 필요할때가 있다.
MemberServiceImpl memberService = ac.getBean("memberService",
MemberServiceImpl.class); //구현체로 검색한다.
assertThat(memberService).isInstanceOf(
MemberServiceImpl.class);// 받아온 멤버서비스가 멤버서비스임플의 객체냐? 라고 물어보는 메소드
}
}
@Test
@DisplayName("이름 없이 빈 타입으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class); // 이름빼고 타입으로만 조회해서 가져와본다.
assertThat(memberService).isInstanceOf(
MemberService.class);//
}
isInstanceOf(인터페이스.class)가 더 깔끔한것 같다. 어차피 해당 인터페이스의 구현체이므로.
테스트는 항상 실패 테스트도 생성해야한다.
@Test // 조회가 안될때 -> 항상 테스트는 실패 테스트를 만들어줘야한다.
@DisplayName("빈 이름으로 조회가 안될때")
void findBeanByNameX() {
MemberService memberService = ac.getBean("xxx", MemberService.class); // xxx란 빈을 등록한적없기에 오류가 발생할것이다.
}
다음과 같이 코드를 짜고 실행 시켜보면 실패가 뜰것이다.
아래와 같은 예외를 보여주면서
이 예외가 떠야 성공라는것을 코드로 구현한다.
org.junit.jupiter.api.Assertions.assertThrows()
assertThrows라는 메소드를 사용할것인데 이 메소드는 junit안에 존재한다.
기니까 static import해서 줄여주고 사용한다.
//assertThrows() 메소드를 사용할것인데 , 이 메소드는 Assertions 안에 있다. , 코드가 길어지니까 Assertions(Junit버전)도 static import해준다.
// org.junit.jupiter.api.Assertions.assertThrows()
실패테스트 코드 전체
@Test // 조회가 안될때 -> 항상 테스트는 실패 테스트를 만들어줘야한다.
@DisplayName("빈 이름으로 조회가 안될때")
void findBeanByNameX() {
//예외가 터져야 성공이니까 왼쪽에는 해당 예외를 적어주고, 오른쪽에는 어떤 로직을 실행했을때? 를 적는다.(람다식으로)
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxx", MemberService.class));
}
스프링 빈 조회 (동일한 타입이 둘 이상일때)
ApplicationContextSameBeanFindTest.java
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(
SameBeanConfig.class); //스프링 컨테이너 생성할건데 기존에 있던 AppConfig.java를 손대서 중복빈을 생성하는것보다 아래서 간단하게 config를 생성해서 넣는다.
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplicate() {
MemberRepository bean = ac.getBean(MemberRepository.class);
}
@Configuration // 중복 빈을 만들기 위한 임시 config 클래스, 클래스안에 클래스를 생성한다는것은 겉에 클래스안에서만 이 클래스를 사용하겠다는 말이다.
static class SameBeanConfig{
@Bean
public MemberRepository memberRepository1() { //빈이름은 다르지만 타입이 동일한 빈이 2개있다.
return new MemoryMemberRepository();
}
// return new MemoryMemberRepository("1000");와 같이 다른 전달인자(파라미터)를 받아 새로운 성능을 가지는 리포지토리를 만들고싶을때
// 이런 중복 타입의 빈을 가지는것은 흔하다고 한다.
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplicate() {
MemberRepository bean = ac.getBean(MemberRepository.class);
}
테스트를 돌렸을시 나오는 예외
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.member.MemberRepository' available: expected single matching bean but found 2: memberRepository1,memberRepository2
라는 예외가 발생한다. ( 하나만 있어야하는데 두개있어서 유니크하지않다.)
예외를 가지고 실패 테스트를 생성해준다.
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplicate() {
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
그럼 같은 타입이 둘 이상일때는?
타입으로만 검색하지말고 빈이름도 전달해서 검색한다.
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있다면, 빈 이름을 지정하면 된다.")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1",
MemberRepository.class);// 간단하게 이름을 지정해주면 된다.
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
같은 타입 모두 조회하고싶다면?
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
//iter 치고 tab 눌러서 아래 반복문 쉽게 생성하기.
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
} // key는 빈 이름 , value에는 타입이 나온다.
System.out.println("beansOfType = " + beansOfType); // map 받은거 그냥 한번 출력도 해본다.
//검증 , 디테일하게 해야하지만 간단하게한다 (2개맞는지 체크)
assertThat(beansOfType.size()).isEqualTo(2);
}
스프링 빈 조회 (상속 관계)
스프링에서 부모타입으로 조회하면 자식타입들까지 다 끌려나온다. ( 대 원칙)
자바의 최상의 부모는 Object이다, 모든 클래스는 보이지않지만 extends Object.class를 하고있는것이다.
ApplicationContextExtendsFindTest.java
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시 , 자식이 둘 이상이 있으면 중복오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회시 , 자식이 둘 이상이 있으면 빈 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
DiscountPolicy discountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class); //이름도 같이 전달
assertThat(discountPolicy).isInstanceOf(DiscountPolicy.class); // DiscountPolicy의 객체(구현체)가 맞는지 체크
}
@Test
@DisplayName("특정 하위 타입으로 조회") // 안좋은방법이다. 구현체로 검색하기때문
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);//찾고자 하는 타입으로 바로검색( 하나밖에 없을떄)
assertThat(bean).isInstanceOf(DiscountPolicy.class); // 인터페이스를 가지고 검증한다. 인터페이스를 상속한 구현체클래스일것이니까.
// assertThat(bean).isInstanceOf(RateDiscountPolicy.class); 구현체클래스로 검증해도 당연히 성공
}
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
//출력해볼수도있음 실제 테스트 케이스를 짤때는 출력물을 남기면 안된다. 출력물을 눈으로 확인안하려고 테스트를 짜는것이기 때문이다.
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + "value = " + beansOfType.get(key));
}
//검증 간단하게 2개인지 체크해서 검증한다.
assertThat(beansOfType.size()).isEqualTo(2);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기 --> Object 타입으로") //스프링컨테이너안에 등록된 모든 빈이 조회된다.
void findAllBeanByObjectType() {
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
@Configuration
static class TestConfig {
//DiscountPolicy로 조회한다면 해당 인터페이스를 상속한 두 빈들이 조회될것이다.
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
테스트코드 작성시 출력하는코드는 빼야한다.
실제 테스트 케이스를 짤때는 출력물을 남기면 안된다. 출력물을 눈으로 확인안하려고 테스트를 짜는것이기 때문이다.
시스템이 성공인지 실패인지 판단하게끔하려고 짜는것이기 때문에,
테스트 자체를 디버깅하고싶을때는 사용한다.
댓글