#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. Да, именно так работает текущая реализация Спока.