Mockito, JUnit и Spring

Я начал узнавать о Мокито только сегодня. Я написал несколько простых тестов (с JUnit, см. Ниже), но я не могу понять, как я могу использовать mock-объект внутри Spring управляет beans. Что лучше всего подходит для работы с Spring. Как я должен вводить в заблуждение зависимость от моего bean?

Вы можете пропустить это до назад к моему вопросу.

Прежде всего, что я узнал. Это очень хорошая статья Mocks Are not Stubs, которая объясняет основы (Mock проверяет проверку проверки поведения). Тогда здесь есть хороший пример Mockito Здесь Легче насмешливо с mockito у нас есть объяснение, что Mockito mock objects являются и макетными, и заглушками.

Здесь Mockito и здесь Matchers вы можете найти больше примеров.

Этот тест

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

отлично работает.

Вернуться к моему вопросу. Здесь Инъекция Mockito издевается над Spring bean, кто-то пытается использовать Springs ReflectionTestUtils.setField(), но здесь Spring Интеграционные тесты, создание Mock Objects, мы рекомендуем изменить контекст Spring.

Я не очень понял последние две ссылки... Может кто-нибудь объяснить мне, что проблема с Spring с Mockito? Что не так с этим решением?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

qaru.site/info/29056/...

РЕДАКТИРОВАТЬ: я был не совсем понятен. Я приведу 3 примера кода, чтобы прояснить себя: Предположим, мы имеем bean HelloWorld с методом printHello() и bean HelloFacade с методом sayHello, который переадресовывает метод HelloWorld printHello().

В первом примере используется контекст Spring и без специального бегуна, используя ReflectionTestUtils для инъекции зависимостей (DI):

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

Как @Noam указал, что существует способ запустить его с явным вызовом MockitoAnnotations.initMocks(this);. Я также остановлю использование контекста Spring в этом примере.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Другой способ сделать это

public class Hello1aTest {

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Noth, что в предыдущем примере нам нужно вручную установить HelloFacadeImpl и назначить его HelloFacade, beacuse HelloFacade - это интерфейс. В последнем примере мы можем просто объявить HelloFacadeImpl, и Mokito создаст его для нас. Недостатком этого подхода является то, что теперь под единым тестом подразумевается класс impl, а не интерфейс.

Ответ 1

Честно говоря, я не уверен, действительно ли я понимаю ваш вопрос: P Я постараюсь прояснить насколько могу, от того, что я получаю от вашего оригинального вопроса:

Во-первых, в большинстве случаев вы не должны беспокоиться о Spring. Вам редко приходится иметь spring, участвующих в написании вашего unit test. В нормальном случае вам нужно всего лишь создать экземпляр тестируемой системы (SUT, целевую аудиторию) в unit test, а также установить зависимости SUT в тесте. Зависимости обычно являются макетами/заглушками.

Ваш оригинальный предложенный способ, а пример 2, 3 точно выполняет то, что я описываю выше.

В некоторых редких случаях (например, тестах интеграции или некоторых специальных модульных тестах) вам необходимо создать контекст приложения spring и получить SUT из контекста приложения. В этом случае, я считаю, вы можете:

1) Создайте свой SUT в spring приложении ctx, получите ссылку на него и приложите к нему макеты

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

или

2) следуйте указаниям, приведенным в вашей ссылке Spring Интеграционные тесты, создание макетов объектов. Этот подход заключается в создании mocks в контексте приложения spring, и вы можете получить mock-объект из приложения ctx для выполнения вашей проверки/проверки:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Оба пути должны работать. Основное различие заключается в том, что первый случай будет иметь зависимости, вводимые после прохождения spring жизненного цикла и т.д. (Например, bean инициализация), в то время как последний случай вводится перед вызовами. Например, если ваш SUT реализует spring InitializingBean, а процедура инициализации включает зависимости, вы увидите разницу между этими двумя подходами. Я считаю, что для этих двух подходов нет правильного или неправильного, если вы знаете, что делаете.

Просто добавление, @Mock, @Inject, MocktoJunitRunner и т.д. не нужны в использовании Mockito. Это просто утилиты, которые помогут вам печатать Mockito.mock(Foo.class) и связку вызовов setter.

Ответ 2

Ваш вопрос, кажется, спрашивает, какой из трех приведенных вами примеров является предпочтительным.

Пример 1 с использованием Reflection TestUtils не подходит для тестирования модулей. Вы действительно не хотите загружать контекст spring вообще для unit test. Просто издевайтесь и вводите то, что требуется, как показано в ваших других примерах.

Вы хотите загрузить контекст spring, если хотите провести некоторое тестирование интеграции, однако я бы предпочел использовать @RunWith(SpringJUnit4ClassRunner.class) для выполнения загрузки контекста вместе с @Autowired, если вам нужен доступ к его beans явно.

Пример 2 - действительный подход, и использование @RunWith(MockitoJUnitRunner.class) устраняет необходимость указания метода @Before и явного вызова MockitoAnnotations.initMocks(this);

Пример 3 - это еще один действительный подход, который не использует @RunWith(...). Вы не создали экземпляр класса под тестом HelloFacadeImpl явно, но вы могли бы сделать то же самое с примером 2.

Мое предложение состоит в том, чтобы использовать пример 2 для тестирования вашего устройства, поскольку это уменьшает беспорядок кода. Вы можете вернуться к более подробной конфигурации, если и когда вы вынуждены это сделать.

Ответ 3

Внедрение некоторых новых средств тестирования в Spring 4.2.RC1 позволяет выполнить одно из тестов интеграции Spring, которые не зависят от SpringJUnit4ClassRunner. Ознакомьтесь с этой частью документации.

В вашем случае вы можете написать свой тест интеграции Spring и по-прежнему использовать такие как:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Ответ 4

Вам не нужен MockitoAnnotations.initMocks(this);, если вы используете mockito 1.9 (или новее) - все, что вам нужно, это:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

Аннотация @InjectMocks добавит все ваши mocks в объект MyTestObject.

Ответ 5

Вот мое короткое резюме.

Если вы хотите написать unit test, не используйте приложение Spring, потому что вам не нужны какие-либо реальные зависимости, введенные в класс, который вы тестируете на модуле. Вместо этого используйте mocks либо с аннотацией @RunWith(MockitoJUnitRunner.class) поверх класса, либо с помощью MockitoAnnotations.initMocks(this) в методе @Before.

Если вы хотите написать тест интеграции, используйте:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

Чтобы настроить контекст приложения с помощью базы данных в памяти, например. Обычно вы не используете mocks в тестах интеграции, но вы можете сделать это, используя описанный выше подход MockitoAnnotations.initMocks(this).

Ответ 6

Разница в том, нужно ли создавать экземпляр аннотированного поля @InjectMocks, находится в версии Mockito, а не в том, используете ли вы MockitoJunitRunner или MockitoAnnotations.initMocks. В 1.9, который также будет обрабатывать некоторые инъекции конструктора ваших полей @Mock, он выполнит создание для вас. В более ранних версиях вы должны сами создать экземпляр.

Вот как я делаю модульное тестирование моего Spring beans. Нет проблем. Люди сталкиваются с путаницей, когда хотят использовать конфигурационные файлы Spring, чтобы на самом деле делать инъекции макетов, которые пересекают точку модульных тестов и тестов интеграции.

И, конечно, тестируемая единица - это Impl. Тебе нужно проверить реальную конкретную вещь, верно? Даже если вы объявили его интерфейсом, вам нужно было бы создать реальную вещь, чтобы проверить его. Теперь вы можете попасть в шпионов, которые являются обертками-заглушками/макетами вокруг реальных объектов, но это должно быть для угловых случаев.

Ответ 7

Если вы перенесете проект на Spring Boot 1.4, вы можете использовать новую аннотацию @MockBean для фальсификации MyDependentObject. С помощью этой функции вы можете удалить аннотации Mockito @Mock и @InjectMocks из своего теста.