Другой макет спока не вводится, кроме того, который определен в тесте весенней загрузки

#spring-boot #spock #spring-boot-test

Вопрос:

Я написал тест загрузки Spring в Spock, где введенная зависимость должна что-то делать в конструкторе компонента.

Я создал макет и аннотировал его с помощью @SpringBean в своей спецификации и определил некоторое поведение для макета, а затем я ожидал, что он будет введен в компонент, который зависит от класса, над которым издеваются. Какой-то издевательский экземпляр вводится в компонент, однако я не могу определить поведение, которого я ожидал.

Я могу видеть из (https://spockframework.org/spock/docs/1.3/module_spring.html):

@SpringBean Спока фактически создает прокси-сервер в ApplicationContext, который перенаправляет все в текущий макет экземпляра. Тип прокси-сервера определяется типом аннотированного поля. Прокси-сервер подключается к текущему макету на этапе настройки, поэтому макет должен быть создан при инициализации поля

Похоже, что этот экземпляр не тот же самый, о чем, я думаю, говорится в цитате в документации, но я ожидал, что смогу определить поведение и включить его в компонент, но методы возвращают только значения по умолчанию, которые предполагают, что введенный макет не переопределил его поведение.

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

Обходной путь заключается в том, чтобы явно создать класс и внедрить зависимость, но в тесте, где возникает эта проблема, зависимость должна быть внедрена в зависимость довольно глубоко в иерархии зависимостей.

кто-нибудь может точно определить, чего мне не хватает?

Тест заключается в следующем:

 package com.test.springmockspock

import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

@SpringBootTest
class SpringspockmockApplicationSpec extends Specification {

    @Autowired
    SpringspockmockApplication sut

    @SpringBean
    Factory factory = Mock() {
        getInt() >> 99
    }

    @Autowired
    Factory autowiredFactory

    void 'hello'() {
        when:
        def insideSUTFactory = sut.factory

        then:

        factory.getInt() == 99
//        sut.intFromFactory == 42              // this should have been 99
        autowiredFactory == insideSUTFactory    // true
        factory == insideSUTFactory             // false (but should have been true)

    }
}
 

The Factory class is:

 package com.test.springmockspock;

import org.springframework.stereotype.Component;

@Component
public class Factory {
    public Producer get() {
        return new Producer();
    }

    public int getInt() {
        return 45;
    }
}
 
 package com.test.springmockspock;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;


@ComponentScan("com.test.springmockspock")
@SpringBootApplication
public class SpringspockmockApplication implements CommandLineRunner {

    private final Factory factory;
    private final Producer producer;

    public SpringspockmockApplication(Factory factory) {
        this.factory = factory;
        this.producer = factory.get();
        return;
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringspockmockApplication.class, args);
    }

    public Factory getFactory() {
        return factory;
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Hello world");
    }

    public int getIntFromFactory() {
        return factory.getInt();
    }
}
 

Сборка.gradle.kts

 plugins {
    groovy
    application

    id("org.springframework.boot") version "2.5.0"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"

}

repositories {
    mavenCentral()
}

dependencies {
    // Use the latest Groovy version for Spock testing
    testImplementation("org.codehaus.groovy:groovy:3.0.7")

    // Use the awesome Spock testing and specification framework even with Java
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.spockframework:spock-spring:2.0-M4-groovy-3.0")
    testImplementation("org.spockframework:spock-core:2.0-M4-groovy-3.0")
    testImplementation("junit:junit:4.13.1")
    testRuntimeOnly("net.bytebuddy:byte-buddy:1.11.0") // allows mocking of classes (in addition to interfaces)
    testRuntimeOnly("org.objenesis:objenesis:3.2")      // allows mocking of classes without default constructor (together with ByteBuddy or CGLIB)

    // This dependency is used by the application.
    implementation("com.google.guava:guava:30.0-jre")
    implementation("org.springframework.boot:spring-boot-starter")
}

application {
    mainClass.set("com.test.springmockspock.App")
}
 

Ответ №1:

Вы уже цитировали точную проблему. @SpringBean Будет создан прокси-сервер, который позже будет прикреплен к спецификации, как и в мире Спока, взаимодействия управляются спецификацией, а не отдельными насмешками.

Теперь контекст spring будет инициализирован до того, как макет будет присоединен к спецификации, поэтому вы получите только экземпляр прокси-сервера без каких-либо взаимодействий. Затем этот прокси-сервер вводится в конструктор, где он будет отвечать своим поведением по умолчанию (возвращая значение null или 0). После инициализации контекста насмешки будут прикреплены к спецификации и теперь могут обрабатывать взаимодействия.

Комментарии:

1. Спасибо. Просто чтобы я это понял. Невозможно будет изменить поведение издевательского экземпляра чего-либо, что происходит при создании компонента, кроме явного создания экземпляра компонента и введения зависимостей, где издевательское поведение определено до создания экземпляра?

2. Да, именно так работает текущая реализация Спока.