spock icon indicating copy to clipboard operation
spock copied to clipboard

Context not properly loaded with Spring Boot 3 for WebMvcTest with OpenFeign clients

Open lmouline opened this issue 2 years ago • 5 comments

Describe the bug

When migration Spring Boot to version 3 and Spock framework to version 2.4-M1 with OpenFeign client(s), tests with the WebMvcTest annotation fail to properly load the context.

Please note that the tests with @SpringBootTest are executed successfully.

Seems that the issue also happens for tests with the @DataMongoTest annotation.

To Reproduce

  • Create a Spring Boot 3 project with OpenFeign
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")


testImplementation("org.springframework.boot:spring-boot-starter-test")

testImplementation("org.apache.groovy:groovy:4.0.7")
testImplementation(platform("org.spockframework:spock-bom:2.4-M1-groovy-4.0"))
testImplementation("org.spockframework:spock-core")
testImplementation("org.spockframework:spock-spring")
  • Create a dummy Rest controller that returns whatever you want
@RequestMapping("/dummy")
@RestController
class DummyController {

    @GetMapping
    fun getAllInformation() = listOf(
            Information("info1", "Great information!"),
            Information("info2", "Super important information!"),
            Information("info3", "Rather small information."),
    )

}
  • Create a dummy Feign client
@FeignClient(
   name = "google",
   url = "https://www.google.com/"
)
interface GoogleClient {
    @GetMapping("/")
    fun getHomePage(): String
}
  • Create a WebMvcTest that test the controller
@WebMvcTest([DummyController.class])
class DummyControllerSpec extends Specification {

    @Autowired
    MockMvc mockMvc


    def "get information"() {
        expect:
        mockMvc.perform(get("/dummy"))
            .andExpect(status().is2xxSuccessful())

    }
}

Minimal example with a failing test: demo.zip

Expected behavior

The test should pass

Actual behavior

The test fails with the following cause:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.openfeign.FeignClientFactory' available

(Note that the test with the @SpringBootTest annotation passes :))

Java version

17

Buildtool version

Gradle 7.6

What operating system are you using

Mac

Dependencies


Root project 'demo'

testRuntimeClasspath - Runtime classpath of compilation 'test' (target  (jvm)).
+--- org.springframework.boot:spring-boot-starter-web -> 3.0.1
|    +--- org.springframework.boot:spring-boot-starter:3.0.1
|    |    +--- org.springframework.boot:spring-boot:3.0.1
|    |    |    +--- org.springframework:spring-core:6.0.3
|    |    |    |    \--- org.springframework:spring-jcl:6.0.3
|    |    |    \--- org.springframework:spring-context:6.0.3
|    |    |         +--- org.springframework:spring-aop:6.0.3
|    |    |         |    +--- org.springframework:spring-beans:6.0.3
|    |    |         |    |    \--- org.springframework:spring-core:6.0.3 (*)
|    |    |         |    \--- org.springframework:spring-core:6.0.3 (*)
|    |    |         +--- org.springframework:spring-beans:6.0.3 (*)
|    |    |         +--- org.springframework:spring-core:6.0.3 (*)
|    |    |         \--- org.springframework:spring-expression:6.0.3
|    |    |              \--- org.springframework:spring-core:6.0.3 (*)
|    |    +--- org.springframework.boot:spring-boot-autoconfigure:3.0.1
|    |    |    \--- org.springframework.boot:spring-boot:3.0.1 (*)
|    |    +--- org.springframework.boot:spring-boot-starter-logging:3.0.1
|    |    |    +--- ch.qos.logback:logback-classic:1.4.5
|    |    |    |    +--- ch.qos.logback:logback-core:1.4.5
|    |    |    |    \--- org.slf4j:slf4j-api:2.0.4 -> 2.0.6
|    |    |    +--- org.apache.logging.log4j:log4j-to-slf4j:2.19.0
|    |    |    |    +--- org.slf4j:slf4j-api:1.7.36 -> 2.0.6
|    |    |    |    \--- org.apache.logging.log4j:log4j-api:2.19.0
|    |    |    \--- org.slf4j:jul-to-slf4j:2.0.6
|    |    |         \--- org.slf4j:slf4j-api:2.0.6
|    |    +--- jakarta.annotation:jakarta.annotation-api:2.1.1
|    |    +--- org.springframework:spring-core:6.0.3 (*)
|    |    \--- org.yaml:snakeyaml:1.33
|    +--- org.springframework.boot:spring-boot-starter-json:3.0.1
|    |    +--- org.springframework.boot:spring-boot-starter:3.0.1 (*)
|    |    +--- org.springframework:spring-web:6.0.3
|    |    |    +--- org.springframework:spring-beans:6.0.3 (*)
|    |    |    +--- org.springframework:spring-core:6.0.3 (*)
|    |    |    \--- io.micrometer:micrometer-observation:1.10.2
|    |    |         \--- io.micrometer:micrometer-commons:1.10.2
|    |    +--- com.fasterxml.jackson.core:jackson-databind:2.14.1
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.14.1
|    |    |    |    \--- com.fasterxml.jackson:jackson-bom:2.14.1
|    |    |    |         +--- com.fasterxml.jackson.core:jackson-annotations:2.14.1 (c)
|    |    |    |         +--- com.fasterxml.jackson.core:jackson-databind:2.14.1 (c)
|    |    |    |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.1 (c)
|    |    |    |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1 (c)
|    |    |    |         +--- com.fasterxml.jackson.module:jackson-module-kotlin:2.14.1 (c)
|    |    |    |         +--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.14.1 (c)
|    |    |    |         \--- com.fasterxml.jackson.core:jackson-core:2.14.1 (c)
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.14.1
|    |    |    |    \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.1
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.14.1 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-databind:2.14.1 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
|    |    +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1
|    |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.14.1 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-core:2.14.1 (*)
|    |    |    +--- com.fasterxml.jackson.core:jackson-databind:2.14.1 (*)
|    |    |    \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
|    |    \--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.14.1
|    |         +--- com.fasterxml.jackson.core:jackson-core:2.14.1 (*)
|    |         +--- com.fasterxml.jackson.core:jackson-databind:2.14.1 (*)
|    |         \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
|    +--- org.springframework.boot:spring-boot-starter-tomcat:3.0.1
|    |    +--- jakarta.annotation:jakarta.annotation-api:2.1.1
|    |    +--- org.apache.tomcat.embed:tomcat-embed-core:10.1.4
|    |    +--- org.apache.tomcat.embed:tomcat-embed-el:10.1.4
|    |    \--- org.apache.tomcat.embed:tomcat-embed-websocket:10.1.4
|    |         \--- org.apache.tomcat.embed:tomcat-embed-core:10.1.4
|    +--- org.springframework:spring-web:6.0.3 (*)
|    \--- org.springframework:spring-webmvc:6.0.3
|         +--- org.springframework:spring-aop:6.0.3 (*)
|         +--- org.springframework:spring-beans:6.0.3 (*)
|         +--- org.springframework:spring-context:6.0.3 (*)
|         +--- org.springframework:spring-core:6.0.3 (*)
|         +--- org.springframework:spring-expression:6.0.3 (*)
|         \--- org.springframework:spring-web:6.0.3 (*)
+--- com.fasterxml.jackson.module:jackson-module-kotlin -> 2.14.1
|    +--- com.fasterxml.jackson.core:jackson-databind:2.14.1 (*)
|    +--- com.fasterxml.jackson.core:jackson-annotations:2.14.1 (*)
|    +--- org.jetbrains.kotlin:kotlin-reflect:1.5.32 -> 1.7.22
|    |    \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.22
|    |         +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.7.22
|    |         \--- org.jetbrains:annotations:13.0
|    \--- com.fasterxml.jackson:jackson-bom:2.14.1 (*)
+--- org.jetbrains.kotlin:kotlin-reflect:1.7.22 (*)
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.22
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.22 (*)
|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.22
|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.22 (*)
+--- org.springframework.cloud:spring-cloud-starter-openfeign -> 4.0.0
|    +--- org.springframework.cloud:spring-cloud-starter:4.0.0
|    |    +--- org.springframework.boot:spring-boot-starter:3.0.0 -> 3.0.1 (*)
|    |    +--- org.springframework.cloud:spring-cloud-context:4.0.0
|    |    |    \--- org.springframework.security:spring-security-crypto:6.0.0 -> 6.0.1
|    |    +--- org.springframework.cloud:spring-cloud-commons:4.0.0
|    |    |    \--- org.springframework.security:spring-security-crypto:6.0.0 -> 6.0.1
|    |    \--- org.springframework.security:spring-security-rsa:1.0.11.RELEASE
|    |         \--- org.bouncycastle:bcpkix-jdk15on:1.69
|    |              +--- org.bouncycastle:bcprov-jdk15on:1.69
|    |              \--- org.bouncycastle:bcutil-jdk15on:1.69
|    |                   \--- org.bouncycastle:bcprov-jdk15on:1.69
|    +--- org.springframework.cloud:spring-cloud-openfeign-core:4.0.0
|    |    +--- org.springframework.boot:spring-boot-autoconfigure:3.0.0 -> 3.0.1 (*)
|    |    +--- org.springframework.boot:spring-boot-starter-aop:3.0.0 -> 3.0.1
|    |    |    +--- org.springframework.boot:spring-boot-starter:3.0.1 (*)
|    |    |    +--- org.springframework:spring-aop:6.0.3 (*)
|    |    |    \--- org.aspectj:aspectjweaver:1.9.19
|    |    \--- io.github.openfeign.form:feign-form-spring:3.8.0
|    |         +--- io.github.openfeign.form:feign-form:3.8.0
|    |         |    \--- org.slf4j:slf4j-api:1.7.26 -> 2.0.6
|    |         +--- org.springframework:spring-web:5.1.5.RELEASE -> 6.0.3 (*)
|    |         +--- commons-fileupload:commons-fileupload:1.4
|    |         \--- org.slf4j:slf4j-api:1.7.26 -> 2.0.6
|    +--- org.springframework:spring-web:6.0.2 -> 6.0.3 (*)
|    +--- org.springframework.cloud:spring-cloud-commons:4.0.0 (*)
|    +--- io.github.openfeign:feign-core:12.1
|    \--- io.github.openfeign:feign-slf4j:12.1
|         +--- io.github.openfeign:feign-core:12.1
|         \--- org.slf4j:slf4j-api:2.0.4 -> 2.0.6
+--- org.springframework.boot:spring-boot-starter-test -> 3.0.1
|    +--- org.springframework.boot:spring-boot-starter:3.0.1 (*)
|    +--- org.springframework.boot:spring-boot-test:3.0.1
|    |    \--- org.springframework.boot:spring-boot:3.0.1 (*)
|    +--- org.springframework.boot:spring-boot-test-autoconfigure:3.0.1
|    |    +--- org.springframework.boot:spring-boot:3.0.1 (*)
|    |    +--- org.springframework.boot:spring-boot-test:3.0.1 (*)
|    |    \--- org.springframework.boot:spring-boot-autoconfigure:3.0.1 (*)
|    +--- com.jayway.jsonpath:json-path:2.7.0
|    |    +--- net.minidev:json-smart:2.4.7 -> 2.4.8
|    |    |    \--- net.minidev:accessors-smart:2.4.8
|    |    |         \--- org.ow2.asm:asm:9.1
|    |    \--- org.slf4j:slf4j-api:1.7.33 -> 2.0.6
|    +--- jakarta.xml.bind:jakarta.xml.bind-api:4.0.0
|    |    \--- jakarta.activation:jakarta.activation-api:2.1.0
|    +--- org.assertj:assertj-core:3.23.1
|    |    \--- net.bytebuddy:byte-buddy:1.12.10 -> 1.12.20
|    +--- org.hamcrest:hamcrest:2.2
|    +--- org.junit.jupiter:junit-jupiter:5.9.1
|    |    +--- org.junit:junit-bom:5.9.1
|    |    |    +--- org.junit.jupiter:junit-jupiter:5.9.1 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-api:5.9.1 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-engine:5.9.1 (c)
|    |    |    +--- org.junit.jupiter:junit-jupiter-params:5.9.1 (c)
|    |    |    +--- org.junit.platform:junit-platform-engine:1.9.1 (c)
|    |    |    \--- org.junit.platform:junit-platform-commons:1.9.1 (c)
|    |    +--- org.junit.jupiter:junit-jupiter-api:5.9.1
|    |    |    +--- org.junit:junit-bom:5.9.1 (*)
|    |    |    +--- org.opentest4j:opentest4j:1.2.0
|    |    |    \--- org.junit.platform:junit-platform-commons:1.9.1
|    |    |         \--- org.junit:junit-bom:5.9.1 (*)
|    |    +--- org.junit.jupiter:junit-jupiter-params:5.9.1
|    |    |    +--- org.junit:junit-bom:5.9.1 (*)
|    |    |    \--- org.junit.jupiter:junit-jupiter-api:5.9.1 (*)
|    |    \--- org.junit.jupiter:junit-jupiter-engine:5.9.1
|    |         +--- org.junit:junit-bom:5.9.1 (*)
|    |         +--- org.junit.platform:junit-platform-engine:1.9.1
|    |         |    +--- org.junit:junit-bom:5.9.1 (*)
|    |         |    +--- org.opentest4j:opentest4j:1.2.0
|    |         |    \--- org.junit.platform:junit-platform-commons:1.9.1 (*)
|    |         \--- org.junit.jupiter:junit-jupiter-api:5.9.1 (*)
|    +--- org.mockito:mockito-core:4.8.1
|    |    +--- net.bytebuddy:byte-buddy:1.12.16 -> 1.12.20
|    |    +--- net.bytebuddy:byte-buddy-agent:1.12.16 -> 1.12.20
|    |    \--- org.objenesis:objenesis:3.2
|    +--- org.mockito:mockito-junit-jupiter:4.8.1
|    |    +--- org.mockito:mockito-core:4.8.1 (*)
|    |    \--- org.junit.jupiter:junit-jupiter-api:5.9.1 (*)
|    +--- org.skyscreamer:jsonassert:1.5.1
|    |    \--- com.vaadin.external.google:android-json:0.0.20131108.vaadin1
|    +--- org.springframework:spring-core:6.0.3 (*)
|    +--- org.springframework:spring-test:6.0.3
|    |    \--- org.springframework:spring-core:6.0.3 (*)
|    \--- org.xmlunit:xmlunit-core:2.9.0
+--- org.apache.groovy:groovy:4.0.7
|    \--- org.apache.groovy:groovy-bom:4.0.7
|         \--- org.apache.groovy:groovy:4.0.7 (c)
+--- org.spockframework:spock-bom:2.4-M1-groovy-4.0
|    +--- org.spockframework:spock-core:2.4-M1-groovy-4.0 (c)
|    \--- org.spockframework:spock-spring:2.4-M1-groovy-4.0 (c)
+--- org.spockframework:spock-core -> 2.4-M1-groovy-4.0
|    +--- org.apache.groovy:groovy:4.0.6 -> 4.0.7 (*)
|    +--- org.junit:junit-bom:5.9.0 -> 5.9.1 (*)
|    +--- org.junit.platform:junit-platform-engine -> 1.9.1 (*)
|    \--- org.hamcrest:hamcrest:2.2
\--- org.spockframework:spock-spring -> 2.4-M1-groovy-4.0
     +--- org.apache.groovy:groovy:4.0.6 -> 4.0.7 (*)
     \--- org.spockframework:spock-core:2.4-M1-groovy-4.0 (*)

(c) - dependency constraint
(*) - dependencies omitted (listed previously)

A web-based, searchable dependency report is available by adding the --scan option.

Additional context

No response

lmouline avatar Jan 19 '23 10:01 lmouline

And this works with prior Spring Boot versions? IIRC @WebMvcTest([DummyController.class]) restricts loading of beans, so I'd assume that the @EnableFeignClients is not picked up and you need to explicitly add it to the context.

As you can see by the error message:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cloud.openfeign.FeignClientFactory' available

This means that Spock did correctly invoke the Spring testing infrastructure.

leonard84 avatar Feb 13 '23 20:02 leonard84

It might be related to the old thread: https://github.com/spring-projects/spring-boot/issues/7270#issuecomment-332525848.

szpak avatar Feb 16 '23 10:02 szpak

I've bumped into that case recently. The weird thing is, it worked fine with Spring Boot 2 and Spock 2.3. However, I clearly see that in SB2 the Feign client (GoogleClient in your case) is not instantiated by default (when I try to inject it explicitly into my test, I have No qualifying bean of type 'org.springframework.cloud.openfeign.FeignContext' available). After the migration to SB3 - it is instantiated automatically with the same @WebMvcTest configuration which generates the problem.

@lmouline Could you write one test with JUnit Jupiter and the aforementioned @WebMvcTest configuration to check if it still fails? If yes, I would be looking for the changes in Spring Boot 3 (or some underlying Spring components) as a reason.

szpak avatar Feb 16 '23 11:02 szpak

@leonard84

And this works with prior Spring Boot versions?

Yep it does :)
From the demo project, using the following dependencies (without changing the code) makes it pass:

plugins {
	id("org.springframework.boot") version "2.7.8"
} 
extra["springCloudVersion"] = "2021.0.5"

dependencies {
   testImplementation(platform("org.spockframework:spock-bom:2.3-groovy-4.0"))
}

@szpak

Could you write one test with JUnit Jupiter and the aforementioned @WebMvcTest configuration to check if it still fails?

The test with Junit works :( Here a new version of a demo with both the Spock test and the JUnit one. They are strictly identical. But you can see that only the JUnit one works :/ demo.zip

lmouline avatar Feb 17 '23 08:02 lmouline

Honestly, not sure what the cause is, as Spock is not really involved in that part. It just instantiates Spring's TestContextManager with the Specification and calls it's lifecycle methods at the correct time. Everything else should be handled by Spring (Boot) internals.

leonard84 avatar Feb 20 '23 20:02 leonard84