Programing

테스트 중 @Autowired 비공개 필드 삽입

crosscheck 2020. 11. 24. 07:41
반응형

테스트 중 @Autowired 비공개 필드 삽입


본질적으로 응용 프로그램의 시작 프로그램 인 구성 요소 설정이 있습니다. 다음과 같이 구성됩니다.

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService는 @ServiceSpring 주석으로 주석 처리되고 문제없이 내 런처 클래스에 자동 연결됩니다.

MyLauncher에 대한 jUnit 테스트 케이스를 작성하고 싶습니다. 이렇게하려면 다음과 같은 클래스를 시작했습니다.

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

MyService에 대한 Mock 객체를 생성하여 테스트 클래스의 myLauncher에 삽입 할 수 있습니까? Spring이 자동 연결을 처리하기 때문에 현재 myLauncher에 getter 또는 setter가 없습니다. 가능하다면 게터와 세터를 추가 할 필요가 없습니다. @Beforeinit 메서드를 사용하여 자동 연결 변수에 모의 객체를 주입하도록 테스트 케이스에 지시 할 수 있습니까 ?

내가 이것에 대해 완전히 틀렸다면 자유롭게 말하십시오. 나는 아직 이것에 익숙하지 않습니다. 내 주요 목표는 @Autowiredsetter 메소드를 작성하거나 applicationContext-test.xml파일 을 사용하지 않고도 해당 변수에 모의 객체를 넣는 Java 코드 또는 주석을 갖는 것 입니다. 테스트 .java를 위해 별도의 애플리케이션 콘텐츠를 유지하는 대신 파일 의 테스트 케이스에 대한 모든 것을 유지하는 것이 훨씬 낫습니다.

모의 객체에 Mockito 를 사용하고 싶습니다 . 과거 org.mockito.Mockito에는 Mockito.mock(MyClass.class).


테스트에서 MyLauncher에 모의를 절대적으로 주입 할 수 있습니다. 누군가를 사용하고있는 조롱 프레임 워크를 보여 주면 답변을 빨리 제공 할 수있을 것입니다. mockito를 사용하면 @RunWith (MockitoJUnitRunner.class)를 사용하고 myLauncher에 대한 주석을 사용할 수 있습니다. 아래와 같이 보일 것입니다.

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

허용되는 대답 ( MockitoJUnitRunner사용 @InjectMocks)은 훌륭합니다. 그러나 좀 더 가볍고 (특별한 JUnit 실행기가 없음) 특히 가끔 사용하기 위해 덜 "마 법적"(더 투명 함)을 원한다면 introspection을 사용하여 직접 private 필드를 설정할 수 있습니다.

Spring을 사용하는 경우 이미 이에 대한 유틸리티 클래스가 있습니다. org.springframework.test.util.ReflectionTestUtils

사용은 매우 간단합니다.

ReflectionTestUtils.setField(myLauncher, "myService", myService);

첫 번째 인수는 대상 빈이고 두 번째 인수는 (일반적으로 개인) 필드의 이름이며 마지막 인수는 삽입 할 값입니다.

Spring을 사용하지 않는 경우 이러한 유틸리티 메서드를 구현하는 것은 매우 간단합니다. 이 Spring 클래스를 찾기 전에 사용한 코드는 다음과 같습니다.

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

때로는 @Component생성자 또는 setter 기반 주입을 사용하여 테스트 케이스를 설정하도록 리팩터링 할 수 있습니다 (여전히를 사용할 수 있음 @Autowired). 이제 테스트 스텁 (예 : Martin Fowler의 MailServiceStub )을 대신 구현하여 모의 프레임 워크없이 테스트를 완전히 만들 수 있습니다 .

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

이 기술은 테스트중인 클래스와 테스트중인 클래스가 동일한 패키지에있는 경우 특히 유용합니다. 기본 패키지 전용 액세스 수정자를 사용하여 다른 클래스가 액세스하지 못하도록 할 수 있기 때문입니다. 프로덕션 코드는 여전히있을 수 src/main/java있지만 테스트는 src/main/test디렉터리에있을 수 있습니다.


Mockito가 마음에 들면 MockitoJUnitRunner 를 고맙게 생각할 것 입니다. @Manuel이 보여준 것과 같은 "마법"을 할 수 있습니다.

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

또는 기본 JUnit 실행기를 사용하고 메소드에서 MockitoAnnotations.initMocks ()호출하여 MockitosetUp() 가 주석이 달린 값을 초기화하도록 할 수 있습니다. @InitMocks 의 javadoc 내가 작성한 블로그 게시물 에서 자세한 정보를 찾을 수 있습니다 .


링크를

그런 다음 테스트 케이스를 다음과 같이 작성하십시오.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}

MyLauncher 클래스 (myService 용)에서 자동 연결 작업을 수행하려면 myLauncher를 자동 연결하여 생성자를 호출하는 대신 Spring이 초기화하도록해야합니다. 자동 연결되면 (그리고 myService도 자동 연결됨) Spring (1.4.0 이상)은 테스트에 넣을 수있는 @MockBean 주석을 제공합니다. 이것은 컨텍스트에서 일치하는 단일 빈을 해당 유형의 모의로 대체합니다. 그런 다음 @Before 메서드에서 원하는 조롱을 추가로 정의 할 수 있습니다.

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

MyLauncher 클래스는 수정되지 않은 상태로 유지 될 수 있으며 MyService 빈은 정의한대로 메소드가 값을 반환하는 모의 객체가됩니다.

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

언급 된 다른 방법에 비해 이것의 몇 가지 장점은 다음과 같습니다.

  1. myService를 수동으로 삽입 할 필요가 없습니다.
  2. Mockito 러너 또는 규칙을 사용할 필요가 없습니다.

I'm a new user for Spring. I found a different solution for this. Using reflection and making public necessary fields and assign mock objects.

This is my auth controller and it has some Autowired private properties.

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullanıcıadı veya şifre hatalı");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

And this is my Junit test for AuthController. I'm making public needed private properties and assign mock objects to them and rock :)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

I don't know advantages and disadvantages for this way but this is working. This technic has a little bit more code but these codes can be seperated by different methods etc. There are more good answers for this question but I want to point to different solution. Sorry for my bad english. Have a good java to everybody :)

참고URL : https://stackoverflow.com/questions/16426323/injecting-autowired-private-field-during-testing

반응형