kotest spring jpa repository bean creation failing during unit test

125 Views Asked by At

I'm new to Kotlin and have a simple list application I'm working on.

  • Spring Boot 3.2.1
  • kotest 5.8.0
  • kotest spring extension 1.1.3

Dependencies:

  implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
  implementation("jakarta.validation:jakarta.validation-api")
  implementation("org.flywaydb:flyway-core")
  implementation("org.jetbrains.kotlin:kotlin-reflect")
  implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springDocOpenApiStarterWebmvcUiVersion")
  implementation("org.springframework.boot:spring-boot-starter-actuator")
  implementation("org.springframework.boot:spring-boot-starter-data-jpa")
  //  implementation("org.springframework.boot:spring-boot-starter-security")
  implementation("org.springframework.boot:spring-boot-starter-validation")
  implementation("org.springframework.boot:spring-boot-starter-web")
  runtimeOnly("com.h2database:h2")
  runtimeOnly("org.postgresql:postgresql")
  annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
  testImplementation("com.ninja-squad:springmockk:$springmockkVersion")
  testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
  testImplementation("io.kotest:kotest-assertions-json:$kotestVersion")
  testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
  testImplementation("io.kotest.extensions:kotest-extensions-spring:$kotestExtensionsSpringVersion")
  //  testImplementation("org.springframework.security:spring-security-test")
  testImplementation("org.springframework.boot:spring-boot-starter-test") {
    exclude(group = "org.assertj", module = "assertj-core")
    exclude(group = "org.hamcrest", module = "hamcrest")
    exclude(group = "org.mockito", module = "mockito-core")
    exclude(group = "org.mockito", module = "mockito-junit-jupiter")
  }

I'm writing some basic unit tests using kotest and the kotest spring extension.

However I have an error when trying to have a spring jpa (or crudrepository) bean created in the test context.

This only happens so far with spring repository interface. Not with a simple @Component annotated class. I can't figure out what I'm doing wrong.

Repository:

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository interface LrmListRepository : JpaRepository<LrmList, Long>

Test Class:

import io.kotest.core.spec.style.WordSpec
import io.kotest.extensions.spring.SpringExtension
import net.flyingfishflash.loremlist.domain.lrmlist.data.LrmListRepository
import net.flyingfishflash.loremlist.domain.lrmlist.exceptions.ListNotFoundException
import org.junit.jupiter.api.assertThrows
import org.springframework.test.context.ContextConfiguration

@ContextConfiguration(classes = [LrmListRepository::class])
class LrmListControllerTests(repository: LrmListRepository) : WordSpec() {
  override fun extensions() = listOf(SpringExtension)

  init {
    "SpringExtension" should {
      "have autowired the repository" {
        assertThrows<ListNotFoundException> {
          repository.findById(1L)
        }
      }
    }
  }
}

The test class is based on the example here: https://kotest.io/docs/extensions/spring.html

This test fails to build with this error:

Caused by: org.springframework.test.context.aot.TestContextAotException: Failed to process test class [net.flyingfishflash.loremlist.domain.lrmlist.LrmListControllerTests] for AOT

Caused by: java.lang.IllegalStateException: No constructor or factory method candidate found for Root bean: class [net.flyingfishflash.loremlist.domain.lrmlist.data.LrmListRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null and argument types []

Any insight would be much appreciated thank you!

1

There are 1 best solutions below

1
rollenwiese On

Resolution

I did finally get the controller tests working the way I wanted, using a combination of spring annotations/kotest/mockk.

Hopefully this example helps someone also sifting through the testing landscape in kotlin.

Controller Test Class:

package net.flyingfishflash.loremlist.domain.lrmlist

import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.spec.style.WordSpec
import io.kotest.extensions.spring.SpringExtension
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.verify
import net.flyingfishflash.loremlist.domain.lrmlist.data.LrmList
import net.flyingfishflash.loremlist.domain.lrmlist.exceptions.ListNotFoundException
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get

@WebMvcTest(controllers = [LrmListController::class])
class LrmListControllerTests(mockMvc: MockMvc) : WordSpec() {
  override fun extensions() = listOf(SpringExtension)

  @MockkBean
  lateinit var lrmListService: LrmListService

  init {
    "get /lists/1" should {
      "return 404 when list is not found" {
        val id = 1L
        every { lrmListService.findByIdOrListNotFoundException(id) } throws(ListNotFoundException())
        mockMvc.get("/lists/$id").andExpect { status { isNotFound() } }
        verify(exactly = 1) { lrmListService.findByIdOrListNotFoundException(id) }
      }
      "return 200 when list is found" {
        val id = 1L
        every {
          lrmListService.findByIdOrListNotFoundException(id)
        } returns LrmList(name = "Lorem List Name", description = "Lorem List Description")
        mockMvc.get("/lists/$id").andExpect { status { isOk() } }
        verify(exactly = 1) { lrmListService.findByIdOrListNotFoundException(id) }
      }
    }

    afterTest {
      clearMocks(lrmListService)
    }
  }
}