0

I tried to write a test code using 'rest docs' in Spring Boot with kotlin. I am having an issue with injecting MockMvc to my test class. No qualifying bean of type Shouldn't @AutoConfigureMockMvc, @AutoConfigureRestDoc, @ExtendWith(RestDocumentationExtension::class) annotations be automatically wired? I don't know why the bean is not being auto-injected.

  1. build.gradle.kts
plugins {
    id("org.springframework.boot") version "2.7.15-SNAPSHOT"
    id("io.spring.dependency-management") version "1.0.15.RELEASE"
    // spring rest doc, gradle version 7 이상
    id("org.asciidoctor.jvm.convert") version "3.3.2"
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
    kotlin("plugin.jpa") version "1.6.21"
    kotlin("kapt") version "1.7.10"
}

val asciidoctorExt by configurations.creating


java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
    maven { url = uri("https://repo.spring.io/milestone") }
    maven { url = uri("https://repo.spring.io/snapshot") }
}

dependencies {
    // jpa
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    runtimeOnly("com.h2database:h2")

    // queryDSL
    implementation("com.querydsl:querydsl-jpa:5.0.0")
    kapt("com.querydsl:querydsl-apt:5.0.0:jpa")

    // kotest + mockk
    testImplementation("io.kotest:kotest-runner-junit5-jvm:4.4.3")
    testImplementation("io.kotest:kotest-assertions-core-jvm:4.4.3")
    implementation("io.kotest:kotest-extensions-spring:4.4.3")
    testImplementation("io.mockk:mockk:1.13.5")

    // api doc(rest doc)
    asciidoctorExt("org.springframework.restdocs:spring-restdocs-asciidoctor")

    testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")

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



tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs += "-Xjsr305=strict"
        jvmTarget = "17"
    }
}

//tasks.withType<Test> {
//    useJUnitPlatform()
//}

//tasks.test {
//    outputs.dir(snippetsDir)
//}


val snippetsDir by extra {
    file("build/generated-snippets")
}

tasks {
    asciidoctor {
        dependsOn(test)
        configurations("asciidoctorExt")
        baseDirFollowsSourceFile()  // 3
        inputs.dir(snippetsDir)

    }
    register<Copy>("copyDocument") {  // 4
        dependsOn(asciidoctor)
        from(file("build/docs/asciidoc/index.html"))
        into(file("src/main/resources/static/docs"))
    }


    bootJar {
        dependsOn("copyDocument")  // 5
    }
}
  1. test code
@AutoConfigureMockMvc
@AutoConfigureRestDocs
@ExtendWith(RestDocumentationExtension::class)
class ControllerTest(
    @Autowired
    private val mockMvc: MockMvc,
    
    @MockBean private val lectureService: LectureService,
) : BehaviorSpec({
     ...
     ...
    val result = mockMvc.post("/api/lectures") {
                contentType = MediaType.APPLICATION_JSON
                content = objectMapper.writeValueAsString(lectureCreateRequest)
            }.andExpect {
                status { isCreated() }
            }.andReturn()

})
  1. error message
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.lecturereservationsystem.lecture.controller.LectureControllerTest': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowire(AbstractAutowireCapableBeanFactory.java:387)
    at io.kotest.spring.SpringAutowireConstructorExtension.instantiate(SpringListener.kt:136)
    at io.kotest.engine.spec.InstantiateSpecKt.createAndInitializeSpec(instantiateSpec.kt:22)
    at io.kotest.engine.spec.SpecExecutor.createInstance(SpecExecutor.kt:53)
    at io.kotest.engine.spec.SpecExecutor.execute(SpecExecutor.kt:40)
    at io.kotest.engine.launchers.DefaultSpecLauncher$sequential$$inlined$forEach$lambda$1$1.invokeSuspend(DefaultSpecLauncher.kt:56)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1801)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1357)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
    ... 13 more

21:37:51.514 [SpringContextShutdownHook] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@58ceb491, started on Mon Jul 24 21:37:51 KST 2023

Process finished with exit code 255

Error creating bean with name 'com.example.lecturereservationsystem.lecture.controller.LectureControllerTest': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
  • 1
    Try adding `@SpringBootTest` to `ControllerTest`. `@AutoConfigureMockMvc` and `@AutoConfigureRestDocs` are Spring Boot features but your test is a "plain" Spring Framework test at the moment and isn't using Spring Boot's testing support. – Andy Wilkinson Jul 24 '23 at 15:54
  • Auto configuring mockmvc only makes sense in a web based test. You would also need something like `@WebMvcTest` to launch a web based test . – M. Deinum Jul 24 '23 at 18:05
  • I also believe that Mockk won't get autowired with `@MockBean`, that's for Mockito Mocks – LeoColman Jul 24 '23 at 18:20
  • @AndyWilkinson If SpringBootTest or WebMvcTest annotation is attached and used, MockMvc NullPointerException Error occurs. Error message is 'Cannot invoke "org.springframework.restdocs.StandardRestDocumentationContext.getAndIncrementStepCount()" because "this.context" is null' – rwabe213 Jul 25 '23 at 07:57
  • @M.Deinum If SpringBootTest or WebMvcTest annotation is attached and used, MockMvc NullPointerException Error occurs. Error message is 'Cannot invoke "org.springframework.restdocs.StandardRestDocumentationContext.getAndIncrementStepCount()" because "this.context" is null' – rwabe213 Jul 25 '23 at 07:57
  • Remove `@ExtendWith(RestDocumentationExtension::class)`. You don't need both it and `@AutoConfigureRestDocs`. There's an [example in Boot's reference docs](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc) that may be of interest. – Andy Wilkinson Jul 25 '23 at 08:46
  • @AndyWilkinson The same error occurs even if you use only WebMvcTest and AutoConfigureRestDocs annotations like the document. 'Cannot invoke "org.springframework.restdocs.StandardRestDocumentationContext.getAndIncrementStepCount()" because "this.context" is null' – rwabe213 Jul 25 '23 at 11:51
  • I've just noticed `BehaviorSpec` and your use of Kotest. I suspect that's the problem. I looks like it's not calling the test lifecycle methods on RestDocsTestExecutionListener that manage the RestDocumentationContext. https://github.com/kotest/kotest/issues/886 should have taken care of that but it appears that it has not. – Andy Wilkinson Jul 25 '23 at 13:53

0 Answers0