Development Tip

Mockito로 new () 호출이있는 테스트 클래스

yourdevel 2020. 12. 29. 08:00
반응형

Mockito로 new () 호출이있는 테스트 클래스


LoginContext ()를 인스턴스화하는 new () 호출이 포함 된 레거시 클래스가 있습니다.

public class TestedClass {
  public LoginContext login(String user, String password) {
    LoginContext lc = new LoginContext("login", callbackHandler);
  }
}

인스턴스화하기 전에 JAAS 보안 항목을 설정해야하므로 Mockito를 사용하여 LoginContext를 모의하기 위해이 클래스를 테스트하고 싶지만 LoginContext를 외부화하기 위해 login () 메서드를 변경하지 않고이를 수행하는 방법을 잘 모르겠습니다. Mockito를 사용하여 LoginContext 클래스를 모의 할 수 있습니까?


미래를 위해 나는 Eran Harel의 대답을 추천 new할 것입니다 (조롱 될 수있는 공장으로 이동 하는 리팩토링 ). 그러나 원본 소스 코드를 변경하지 않으려면 매우 편리하고 고유 한 기능인 spies를 사용하십시오 . 로부터 문서 :

실제 물체의 스파이를 만들 수 있습니다. 스파이를 사용하면 실제 메서드가 호출됩니다 (메서드가 스텁되지 않은 경우).

예를 들어 레거시 코드를 처리 할 때 실제 스파이는 신중하고 가끔 사용해야합니다 .

귀하의 경우에는 다음과 같이 작성해야합니다.

TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);

나는 Eran Harel의 해결책을 위해 최선을 다하고 있으며 가능하지 않은 경우 Tomasz Nurkiewicz의 스파이 제안은 훌륭합니다. 그러나 둘 다 적용되지 않는 상황이 있다는 점은 주목할 가치가 있습니다. 예를 들어 login방법이 약간 "삐걱 거리는"경우 :

public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
}

... 그리고 이것은 LoginContext자체 메서드 에 대한 새로운 초기화를 추출 하고 앞서 언급 한 솔루션 중 하나를 적용 하기 위해 리팩토링 할 수없는 오래된 코드였습니다 .

완전성을 위해 세 번째 기술인 PowerMock사용 하여new 연산자가 호출 될 때 모의 객체를 주입하는 방법을 언급 할 가치가 있습니다. PowerMock은 은색 총알이 아닙니다. 모의 클래스에 바이트 코드 조작을 적용하여 작동합니다. 테스트 된 클래스가 바이트 코드 조작 또는 반사를 사용하고 적어도 제 개인적인 경험에서 테스트에 성능 저하를 유발하는 것으로 알려진 경우 어리석은 연습이 될 수 있습니다. 다시 말하지만, 다른 옵션이없는 경우 유일한 옵션은 좋은 옵션이어야합니다.

@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {

    @Test
    public void testLogin() {
        LoginContext lcMock = mock(LoginContext.class);
        whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
        TestedClass tc = new TestedClass();
        tc.login ("something", "something else");
        // test the login's logic
    }
}

팩토리를 사용하여 로그인 컨텍스트를 만들 수 있습니다. 그런 다음 공장을 모의하고 테스트를 위해 원하는 것을 반환 할 수 있습니다.

public class TestedClass {
  private final LoginContextFactory loginContextFactory;

  public TestedClass(final LoginContextFactory loginContextFactory) {
    this.loginContextFactory = loginContextFactory;
  }

  public LoginContext login(String user, String password) {
    LoginContext lc = loginContextFactory.createLoginContext();
  }
}

public interface LoginContextFactory {
  public LoginContext createLoginContext();
}

    public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
  }

-시험 등급 :

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(TestedClass.class)
    public class TestedClassTest {

        @Test
        public void testLogin() {
            LoginContext lcMock = mock(LoginContext.class);
            whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
//comment: this is giving mock object ( lcMock )
            TestedClass tc = new TestedClass();
            tc.login ("something", "something else"); ///  testing this method.
            // test the login's logic
        }
    }

When calling the actual method tc.login ("something", "something else"); from the testLogin() { - This LoginContext lc is set to null and throwing NPE while calling lc.doThis();


Not that I know of, but what about doing something like this when you create an instance of TestedClass that you want to test:

TestedClass toTest = new TestedClass() {
    public LoginContext login(String user, String password) {
        //return mocked LoginContext
    }
};

Another option would be to use Mockito to create an instance of TestedClass and let the mocked instance return a LoginContext.


In situations where the class under test can be modified and when it's desirable to avoid byte code manipulation, to keep things fast or to minimise third party dependencies, here is my take on the use of a factory to extract the new operation.

public class TestedClass {

    interface PojoFactory { Pojo getNewPojo(); }

    private final PojoFactory factory;

    /** For use in production - nothing needs to change. */
    public TestedClass() {
        this.factory = new PojoFactory() {
            @Override
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
    }

    /** For use in testing - provide a pojo factory. */
    public TestedClass(PojoFactory factory) {
        this.factory = factory;
    }

    public void doSomething() {
        Pojo pojo = this.factory.getNewPojo();
        anythingCouldHappen(pojo);
    }
}

With this in place, your testing, asserts and verify calls on the Pojo object are easy:

public  void testSomething() {
    Pojo testPojo = new Pojo();
    TestedClass target = new TestedClass(new TestedClass.PojoFactory() {
                @Override
                public Pojo getNewPojo() {
                    return testPojo;
                }
            });
    target.doSomething();
    assertThat(testPojo.isLifeStillBeautiful(), is(true));
}

The only downside to this approach potentially arises if TestClass has multiple constructors which you'd have to duplicate with the extra parameter.

For SOLID reasons you'd probably want to put the PojoFactory interface onto the Pojo class instead, and the production factory as well.

public class Pojo {

    interface PojoFactory { Pojo getNewPojo(); }

    public static final PojoFactory productionFactory = 
        new PojoFactory() {
            @Override 
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };

ReferenceURL : https://stackoverflow.com/questions/5920153/test-class-with-a-new-call-in-it-with-mockito

반응형