Mockito: как проверить метод был вызван на объект, созданный в рамках метода?
Я новичок в Mockito.
Учитывая класс ниже, как я могу использовать Mockito для проверки того, что someMethod
был вызван ровно один раз после вызова foo
?
public class Foo
{
public void foo(){
Bar bar = new Bar();
bar.someMethod();
}
}
Я хотел бы сделать следующий проверочный звонок
verify(bar, times(1)).someMethod();
где bar
является издеваемым экземпляром bar
.
Ответы
Ответ 1
Инъекция зависимостей
Если вы добавляете экземпляр Bar или factory, который используется для создания экземпляра Bar (или один из других 483 способов сделать это), у вас будет доступ, необходимый для выполнения теста.
Factory Пример:
Учитывая класс Foo, написанный следующим образом:
public class Foo {
private BarFactory barFactory;
public Foo(BarFactory factory) {
this.barFactory = factory;
}
public void foo() {
Bar bar = this.barFactory.createBar();
bar.someMethod();
}
}
в вашем методе тестирования вы можете ввести BarFactory следующим образом:
@Test
public void testDoFoo() {
Bar bar = mock(Bar.class);
BarFactory myFactory = new BarFactory() {
public Bar createBar() { return bar;}
};
Foo foo = new Foo(myFactory);
foo.foo();
verify(bar, times(1)).someMethod();
}
Бонус: Это пример того, как TDD может управлять дизайном вашего кода.
Ответ 2
Классический ответ: "У вас нет". Вы проверяете открытый API Foo
, а не его внутренние элементы.
Есть ли какое-либо поведение объекта Foo
(или, что еще хуже, какой-либо другой объект в среде), на который влияет foo()
? Если да, проверьте это. А если нет, что делает этот метод?
Ответ 3
Если вы не хотите использовать DI или фабрики. Вы можете реорганизовать свой класс немного сложнее:
public class Foo {
private Bar bar;
public void foo(Bar bar){
this.bar = (bar != null) ? bar : new Bar();
bar.someMethod();
this.bar = null; // for simulating local scope
}
}
И ваш тестовый класс:
@RunWith(MockitoJUnitRunner.class)
public class FooTest {
@Mock Bar barMock;
Foo foo;
@Test
public void testFoo() {
foo = new Foo();
foo.foo(barMock);
verify(barMock, times(1)).someMethod();
}
}
Затем класс, вызывающий ваш метод foo, сделает это следующим образом:
public class thirdClass {
public void someOtherMethod() {
Foo myFoo = new Foo();
myFoo.foo(null);
}
}
Как вы можете видеть при вызове метода таким образом, вам не нужно импортировать класс Bar в любой другой класс, который вызывает ваш метод foo, который, возможно, вы хотите.
Конечно, недостатком является то, что вы разрешаете вызывающему абоненту устанавливать объект Bar.
Надеюсь, что это поможет.
Ответ 4
Решение для вашего примера кода с помощью PowerMockito.whenNew
- mockito-all 1.10.8
- powermock-core 1.6.1
- powermock-module-junit4 1.6.1
- powermock-api-mockito 1.6.1
- junit 4.12
FooTest.java
package foo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
//Both @PrepareForTest and @RunWith are needed for 'whenNew' to work
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {
// Class Under Test
Foo cut;
@Mock
Bar barMock;
@Before
public void setUp() throws Exception {
cut = new Foo();
}
@After
public void tearDown() {
cut = null;
}
@Test
public void testFoo() throws Exception {
// Setup
PowerMockito.whenNew(Bar.class).withNoArguments()
.thenReturn(this.barMock);
// Test
cut.foo();
// Validations
Mockito.verify(this.barMock, Mockito.times(1)).someMethod();
}
}
Выход JUnit ![JUnit Output]()
Ответ 5
Я думаю, что Mockito @InjectMocks
- это путь.
В зависимости от вашего намерения вы можете использовать:
- Впрыск конструктора
- Ввод установки свойств
- Полевая инъекция
Дополнительная информация в документах
Ниже приведен пример с инъекцией поля:
Классы:
public class Foo
{
private Bar bar = new Bar();
public void foo()
{
bar.someMethod();
}
}
public class Bar
{
public void someMethod()
{
//something
}
}
Тестовое задание:
@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
@Mock
Bar bar;
@InjectMocks
Foo foo;
@Test
public void FooTest()
{
doNothing().when( bar ).someMethod();
foo.foo();
verify(bar, times(1)).someMethod();
}
}
Ответ 6
Да, если вам действительно нужно/нужно это сделать, вы можете использовать PowerMock. Это следует рассматривать как последнее средство. С PowerMock вы можете заставить его вернуть макет из вызова конструктору. Затем выполните проверку на макет. Тем не менее, csturtz - это "правильный" ответ.
Вот ссылка на Макет строительства новых объектов
Ответ 7
Другим простым способом было бы добавить некоторый оператор журнала в bar.someMethod(), а затем убедиться, что вы можете видеть указанное сообщение, когда ваш тест выполнен, см. Примеры здесь: Как сделать JUnit assert в сообщении в журнале
Это особенно удобно, когда ваш Bar.someMethod() является private
.