Класс тестирования с новым вызовом() в нем с помощью Mockito
У меня есть унаследованный класс, который содержит вызов new() для создания экземпляра LoginContext():
public class TestedClass {
public LoginContext login(String user, String password) {
LoginContext lc = new LoginContext("login", callbackHandler);
}
}
Я хочу протестировать этот класс, используя Mockito, чтобы издеваться над LoginContext, поскольку для его создания необходимо создать среду безопасности JAAS, но я не уверен, как это сделать, не изменяя метод login() для экстернализации LoginContext. Возможно ли использование Mockito для издевательства над классом LoginContext?
Ответы
Ответ 1
В будущем я бы рекомендовал ответить Эрану Харелу (рефакторинг, перемещающий new
в factory, который можно издеваться). Но если вы не хотите менять исходный код, используйте очень удобную и уникальную функцию: spies. Из документации:
Вы можете создавать шпионы реальных объектов. Когда вы используете шпиона, вызывается метод real (если только метод не был заглушен).
Настоящие шпионы следует использовать тщательно и иногда, например, при работе с устаревшим кодом.
В вашем случае вы должны написать:
TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);
Ответ 2
Я все для решения Eran Harel, и в тех случаях, когда это невозможно, предложение Томаша Нуркевича о шпионаже превосходно. Однако стоит отметить, что есть ситуации, когда ни одна из них не применима. Например. если метод 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 logic
}
}
Ответ 3
Вы можете использовать factory для создания контекста входа. Затем вы можете высмеять factory и вернуть все, что хотите для своего теста.
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();
}
Ответ 4
Не то, что я знаю, но как насчет того, чтобы делать что-то подобное, когда вы создаете экземпляр TestedClass, который вы хотите проверить:
TestedClass toTest = new TestedClass() {
public LoginContext login(String user, String password) {
//return mocked LoginContext
}
};
Другой вариант - использовать Mockito для создания экземпляра TestedClass и позволить посмеяемому экземпляру вернуть LoginContext.
Ответ 5
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 logic
}
}
При вызове фактического метода tc.login ("something", "something else");
из testLogin() {
- Этот LoginContext lc установлен в значение null и бросает NPE при вызове lc.doThis();
Ответ 6
В ситуациях, когда тестируемый класс может быть изменен, и когда желательно избегать манипулирования байтовым кодом, чтобы ускорить работу или свести к минимуму зависимости сторонних разработчиков, вот мой подход к использованию factory для извлечения new
работа.
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);
}
}
При этом ваше тестирование, подтверждение и проверка вызовов объекта Pojo легко:
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));
}
Единственный недостаток этого подхода потенциально возникает, если TestClass
имеет несколько конструкторов, которые вам придется дублировать с дополнительным параметром.
По соображениям SOLID вы, вероятно, захотите поместить интерфейс PojoFactory в класс Pojo, а также в производство factory.
public class Pojo {
interface PojoFactory { Pojo getNewPojo(); }
public static final PojoFactory productionFactory =
new PojoFactory() {
@Override
public Pojo getNewPojo() {
return new Pojo();
}
};