Статьи

Spock 1.2 — беспроблемная насмешка Spring beans в интеграционных тестах

Узнайте, как автоматически внедрять макеты и шпионы Спока в контекст Spring с помощью Spock 1.2.

Заглушки / шутки / шпионы в Споке (и их жизненный цикл) всегда были тесно связаны с классом Specification Спока. Их можно было создать только в тестовом классе. Поэтому использование общих предопределенных макетов (как в модульных, так и в интеграционных тестах) было проблематичным.

Ситуация была немного улучшена в Spock 1.1, но только с новым Spock 1.2 (1.2-RC1 как время написания) с использованием подсистемы @SpringMock Spock в интеграционных тестах на основе Spring так же просто, как использовать @SpringMock для Mockito mocks в Spring Загрузочный. Давайте проверим это.

Кстати, чтобы быть более передовым в дополнение к Spock 1.2-RC1, я буду использовать Spring Boot 2.1.0.M2, Spring 5.1.0.RC2 и Groovy 2.5.2 (но все должно работать со стабильными версиями Spring ( Boot) и Groovy 2.4).

Спок

Еще кое-что. Для простоты в этой статье я буду использовать термин «макет» для обозначения заглушек и шпионов. Они отличаются по поведению, однако, по объему внедрения его в контекст Spring в тестах Спока это обычно не имеет значения.

Спок 1.1 — ручной способ

Благодаря работе Леонарда Брюнингса , издевательства в Споке были отделены от класса Specification . Наконец-то можно было создать их снаружи и позже подключить к работающему тесту. Это было краеугольным камнем использования макетов Спока в весеннем (или любом другом) контексте.

В этом примере кода у нас есть класс ShipDatabase который использует OwnShipIndex и EnemyShipIndex (конечно, EnemyShipIndex конструктором :)), чтобы возвратить агрегированную информацию обо всех известных судах, соответствующих имени.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//@ContextConfiguration just for simplification, @(Test)Configuration is usually more convenient for Spring Boot tests
//Real beans can exist in the context or not
@ContextConfiguration(classes = [ShipDatabase, TestConfig/*, OwnShipIndex, EnemyShipIndex*/])
class ShipDatabase11ITSpec extends Specification {
 
    private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)"
    private static final String BORTAS_ENTERA = "IKS Bortas Entera"
 
    @Autowired
    private OwnShipIndex ownShipIndexMock
 
    @Autowired
    private EnemyShipIndex enemyShipIndexMock
 
    @Autowired
    private ShipDatabase shipDatabase
 
    def "should find ship in both indexes"() {
        given:
            ownShipIndexMock.findByName("Enter") >> [ENTERPRISE_D]
            enemyShipIndexMock.findByName("Enter") >> [BORTAS_ENTERA]
        when:
            List<String> foundShips = shipDatabase.findByName("Enter")
        then:
            foundShips == [ENTERPRISE_D, BORTAS_ENTERA]
    }
 
    static class TestConfig {
        private DetachedMockFactory detachedMockFactory = new DetachedMockFactory()
 
        @Bean
        @Primary    //if needed, beware of consequences
        OwnShipIndex ownShipIndexStub() {
            return detachedMockFactory.Stub(OwnShipIndex)
        }
 
        @Bean
        @Primary    //if needed, beware of consequences
        EnemyShipIndex enemyShipIndexStub() {
            return detachedMockFactory.Stub(EnemyShipIndex)
        }
    }
}

Макеты создаются в отдельном классе (вне Specification ), и поэтому необходимо использовать DetachedMockFactory (или, альтернативно, SpockMockFactoryBean ). Эти макеты должны быть присоединены (и отсоединены) к тестовому экземпляру (экземпляру Specification ), но он автоматически обрабатывается spock-spring (по состоянию на 1.1). Для общих MockUtil.attachMock() создаваемых извне, для его работы необходимо использовать MockUtil.attachMock() и mockUtil.detachMock() .

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

Спок 1.2 — первоклассная поддержка

Spring Boot 1.4 привнес новое качество в интеграционное тестирование с макетами (Mockito). Он использовал идею, первоначально представленную в Springockito еще в 2012 году (когда конфигурация Spring была в основном написана на XML :)), чтобы автоматически вводить макеты (или шпионы) в контекст Spring (Boot). Команда Spring Boot расширила эту идею, и благодаря тому, что она является внутренне поддерживаемой функцией, она (обычно) работает надежно, просто добавив одну или две аннотации в тест.

Подобный механизм на основе аннотации встроен в Спок 1.2 .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//@ContextConfiguration just for simplification, @(Test)Configuration is usually more convenient for Spring Boot tests
//Real beans can exist in the context or not
@ContextConfiguration(classes = [ShipDatabase/*, OwnShipIndex, EnemyShipIndex*/])
class ShipDatabaseITSpec extends Specification {
 
    private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)"
    private static final String BORTAS_ENTERA = "IKS Bortas Entera"
 
    @SpringBean
    private OwnShipIndex ownShipIndexMock = Stub()  //could be Mock() if needed
 
    @SpringBean
    private EnemyShipIndex enemyShipIndexMock = Stub()
 
    @Autowired
    private ShipDatabase shipDatabase
 
    def "should find ship in both indexes"() {
        given:
            ownShipIndexMock.findByName("Enter") >> [ENTERPRISE_D]
            enemyShipIndexMock.findByName("Enter") >> [BORTAS_ENTERA]
        when:
            List<String> foundShips = shipDatabase.findByName("Enter")
        then:
            foundShips == [ENTERPRISE_D, BORTAS_ENTERA]
    }
}

Там не так много, чтобы быть добавленным. @SpringBean инструктирует Спока вводить макет в контекст Spring. Точно так же @SpringSpy оборачивает настоящий боб шпионом. В случае @SpringBean требуется инициализировать поле, чтобы сообщить Споку, планируем ли мы использовать заглушку или макет.

Кроме того, существует также более общая аннотация @StubBeans для замены всех определенных bean-компонентов на заглушки. Тем не менее, я планирую рассказать об этом отдельно в другом сообщении в блоге.

Ограничения

Для тех из вас, кто надеется переписать все макеты Мокито в макеты Спока в ваших тестах на Спока, сразу после лекции этой статьи есть слово предупреждения. Насмешки Спока — из-за их характера и связи со Specification — имеют некоторые ограничения. Реализация под капотом создает прокси, который внедряется в контекст Spring, который (потенциально) заменяет реальные бины (заглушки / насмешки) или оборачивает их (шпионы). Этот прокси является общим для всех тестов в определенном классе тестов (спецификаций). Фактически, он также может охватывать другие тесты с теми же объявлениями bean / mock в ситуации, в которой Spring может кэшировать контекст (аналогично ситуации с mockito или тестами интеграции Spring в целом).

Однако, что действительно важно, прокси-сервер подключается к тестам непосредственно перед его выполнением и отключается сразу после него. Следовательно, фактически каждый тест имеет свой собственный фиктивный экземпляр (его нельзя применить к полям @Shared ), и, например, проблематично группировать взаимодействия из разных тестов и проверять их вместе (что обычно довольно разумно, но может привести к некоторым дублирование). Тем не менее, с помощью блока setup (или встроенной заглушки) можно разделить ожидаемость заглушки и взаимодействия.

Резюме

В Spock 1.2 наконец-то появилась поддержка Stubs / mocks / spies от Spock для их беспрепятственного использования в контексте Spring, что сравнимо с тем, что предоставляется в Spring Boot для Mockito. Достаточно добавить модуль spock-spring в зависимости проекта (времени выполнения). Несмотря на некоторые ограничения, в ваших тестах Spock (интеграция) на смешивание подсистемы mocking собственной Spock с внешними системами mocking (такими как Mockito) это на один пункт меньше. И что приятно, это должно работать и в простых тестах Spring Framework (не только в тестах Spring Boot). Такая же функция была реализована для Guice (но я ее не тестировал).

Кроме того, в Spock 1.2 также внесены некоторые другие изменения, в том числе улучшена поддержка Java 9+, и стоит попробовать в своем тестовом наборе (и, конечно, сообщать о любых потенциально обнаруженных ошибках регрессии :)).

Еще одна хорошая новость. В дополнение к работе Леонарда, которая сделала возможной Spock 1.2 и легион репортеров ошибок и пиар-спонсоров, с недавнего времени есть и другие приверженцы, которые работают над тем, чтобы сделать Спока еще лучше. Некоторые из них вы можете узнать из других популярных проектов FOSS. Более того, Spock 1.2 (предварительно) планируется стать последней версией, основанной на JUnit 4, а следующей стабильной версией Spock может стать 2.0, использующая JUnit 5 и (среди прочих) его собственную способность выполнять тесты параллельно.

Примеры написаны с использованием Spock 1.2-RC1. Он будет обновлен до 1.2-финальной после выхода. Исходный код доступен на GitHub.

Смотрите оригинальную статью здесь: Spock 1.2 — беспроблемная насмешка Spring beans в интеграционных тестах

Мнения, высказанные участниками Java Code Geeks, являются их собственными.