How to mock repository so it doesn't instert or pull data from DB - spring-boot

So my issue is that in my SpringBoot REST application im testing my RestController. The problem is that i don't know how to mock the repository so it doesn't get or puts data into the DB. I'm using Kotlin and Mockk for mocking
Here is my Repository
#Repository
interface StandingOrderRepository: CrudRepository<StandingOrder, Int> {
fun findByNameAndVariableSymbol(name: String, variableSymbol: String): List<StandingOrder>
fun findByValidFromBetween(fromDate: String, toDate: String): List<StandingOrder>
fun findByValidFromAfter(fromDate: String) : List<StandingOrder>
}
And here is my Test
#SpringBootTest
#AutoConfigureMockMvc
internal class StandingOrderResourceTest {
#Autowired
lateinit var mockMvc: MockMvc
#Autowired
lateinit var objectMapper: ObjectMapper
private val standingOrderMapper = mockk<StandingOrderMapper>()
private val standingOrderRepository = mockk<StandingOrderRepository>()
private val standingOrderServiceImpl = mockk<StandingOrderServiceImpl>()
private val standingOrderResource = StandingOrderResource(standingOrderServiceImpl)
val baseUrl = "/api"
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
standingOrderResource.getStandingOrders()
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
//standingOrderResource.getStandingOrders() shouldBe listOf(standingOrderDto)
}
}
}
The problem is if i Make a API call or invoke the mocked repository it still gets actual data from DB

In your test code you should try to use method whenever() from org.mockito.kotlin for stubbing StandingOrderRepository's method call.
For example your code for stubbing will looks something like this
whenever(standingOrderRepository.findByNameAndVariableSymbol(any(),any())).thenReturn(listOf(StandingOrder(...)))
UPD: So you use Mockk, then you shuold use method every instead whenever from mockito.

So this is how i made it work maybe the issue was on my side how i was trying to use it #Anton Tokmakov was correct here is how i did it
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension::class)
internal class StandingOrderResourceTest #Autowired constructor(
val mockMvc: MockMvc,
val objectMapper: ObjectMapper,
) {
#MockkBean
private lateinit var standingOrderResource: StandingOrderResource
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
every { standingOrderResource.getStandingOrders() } returns
listOf(standingOrderDto1, standingOrderDto2)
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
.andExpect {
jsonPath("\$..[0]", match(MockMvcResultMatchers.content().json(Gson().toJson(
listOf(
standingOrderDto1,
standingOrderDto2
)), false)))
}
}
}

Related

How to initialize repository from service in Springboot Kotlin

#Service
public class AvailablePolicyService {
#Autowired
private var availablePolicyRepository : AvailablePolicyRepository = **AvailablePolicyRepository()**
fun saveAvailablePolicy(availablePolicy: AvailablePolicy): AvailablePolicy { return availablePolicyRepository.save(availablePolicy) }
fun getAllAvailablePolicy(): List<AvailablePolicy>{ return availablePolicyRepository.findAll() }
fun getAvailablePolicyByPolicyId(policyId: String?): AvailablePolicy? {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyId().equals(policyId)) {
return availablePolicy
}
}
return null
}
fun getAvailablePolicyByPolicyCategory(policyCategory: String?): ArrayList<AvailablePolicy> {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
var availablePolicyCategory = ArrayList<AvailablePolicy>()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyCategory().equals(policyCategory)) {
availablePolicyCategory.add(availablePolicy)
}
}
return availablePolicyCategory
}
}
#Repository
interface AvailablePolicyRepository : MongoRepository<AvailablePolicy, String>
The bolded text shows where the error is showing and it reads "Interface AvailablePolicyRepository does not have constructors". How do I initialize repository from service?
How do I initialize repository from service?
That's the thing. You don't! Spring does it for you:
#Autowired
private lateinit var availablePolicyRepository: AvailablePolicyRepository
Field injection is rather obsolete and you should consider using contructor injection instead.
#Service
class AvailablePolicyService(private val availablePolicyRepository: AvailablePolicyRepository) {...}

Spring test #Transactional with #Nested junit classes

I am encountering a problem in spring transactions: even though I see in the output that a transaction is created and rolled back after each test method; when I check the database content, it still keeps the document that was saved during the transaction.
I ran mongodb instance in replica mode following instructions from here https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set-for-testing/.
My test class is defined as follows:
#Transactional
#AutoConfigureMockMvc
#ActiveProfiles(profiles = ["test"])
#DisplayName("Login Functionality Test")
#SpringBootTest(classes = [TestConfiguration::class])
class LoginFunctionalityTest #Autowired constructor(
private val mockMvc: MockMvc,
private val userRepository: UserRepository,
private val openUserRepository: OpenUserRepository,
private val patientRepository: PatientRepository,
private val passwordEncoder: PasswordEncoder,
private val jwtMatcher: JWTMatcher,
#Value("\${secret:sD1fRUWtBdfA8BNcbf}") private val fastLoginSecret: String
) {
private val existingUsersPassword = "SOME_PASSWORD"
private val userFastLoginTokenSecret = "ANY_SECRET"
private lateinit var existingUser: User
private lateinit var existingOpenUser: OpenUser
private lateinit var existingPatient: Patient
#BeforeEach
fun prepareContext() {
println("Init db")
existingUser = userRepository.save(User(null, now(), "test", "test", "test#test.test", passwordEncoder.encode(existingUsersPassword), UserAuthority.Patient, true, true))
existingOpenUser = openUserRepository.save(OpenUser(null, "ANY", nextExpiryDate()))
existingPatient = patientRepository.save(Patient(null, existingUser))
}
#DisplayName("Login Controller")
#Nested
inner class LoginControllerTest{
#Test
fun `should return unauthorized 401 status code when user logins with not existing email`() {
val invalidEmail = "INVALID_EMAIL"
assertThrows<EmptyResultDataAccessException> { userRepository.findByEmail(invalidEmail) }
mockMvc.post("/auth/login") {
param("username", invalidEmail)
param("password", existingUsersPassword)
}.andExpect {
jsonPath("$.status", equalToIgnoringCase(HttpStatus.UNAUTHORIZED.name))
jsonPath("$.error",StringContains(true,"Invalid login credentials"))
jsonPath("$.data", nullValue())
status { isUnauthorized() }
}
}
...
}
#ActiveProfiles(profiles = ["open-user"])
#DisplayName("Open User Login Controller")
#Nested
inner class OpenUserControllerLoginTest(){
#Test
fun `should return bad request 400 status when any of the required login parameters are missing`(){
val validFastLoginToken = String(Base64.getEncoder().encode(passwordEncoder.encode("$userFastLoginTokenSecret${existingUser.passwordHash}$fastLoginSecret").toByteArray()))
mockMvc
.post("/auth/login/fast") {
param("userId", existingUser.id!!)
param("secret", userFastLoginTokenSecret)
param("fastLoginToken", validFastLoginToken)
}.andExpect {
jsonPath("$.data.isUserActivated", equalTo(existingUser.activated))
jsonPath("$.data.accessToken",jwtMatcher.matches(existingUser))
jsonPath("$.status", equalToIgnoringCase(HttpStatus.OK.name))
jsonPath("$.error", nullValue())
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
}
}
...
}
The output in console has the "Init db" printed between creation and roll back of transaction.
It works correctly only if I move #ActiveProfiles from #Nested class to the outer class. Seems like #ActiveProfiles causes context reload but I don't know why this causes problems with transactions.

NullPointer with Reactive Spring Elastic Repository mocked into a service

This is the snippet of my test class:
#SpringBootTest
#ExtendWith(MockitoExtension::class)
class ResourceSearchServiceTest {
#Mock
lateinit var resourceSearchRepository: ResourceSearchRepository
#InjectMocks
lateinit var resourceSearchService: ResourceSearchService
lateinit var expectedResourceMetadata : ResourceSearchMetadata
#BeforeEach
fun createResourceMetadata() {
expectedResourceMetadata =
ResourceSearchMetadata(
id = UUID.randomUUID(),
pk = UUID.randomUUID(),
metadata = ResourceMetadata(
description = "blabla"
)
)
}
#Test
fun activeResourceMetadataTest() {
Mockito.`when`(resourceSearchRepository.findById(any(UUID::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
Mockito.`when`(resourceSearchRepository.save(any(ResourceSearchMetadata::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
resourceSearchService.updateActiveStatus(expectedResourceMetadata.id, false)
Mockito.verify(resourceSearchRepository, Mockito.times(1)).save(expectedResourceMetadata)
assertThat(expectedResourceMetadata.metadata.active).isFalse
}
}
and this is my service :
#Service
class ResourceSearchService(private val resourceSearchRepository: ResourceSearchRepository) {
fun updateActiveStatus(id: UUID, isActive: Boolean) {
resourceSearchRepository.findById(id).flatMap { resource ->
resource.metadata.active = isActive
resourceSearchRepository.save(resource)
}.map { Success }.defaultIfEmpty(NotFound)
}
}
when updateAciveStatus service method is called from the test class a nullpointer exception is raised by the repository at this line : resourceSearchRepository.findById(id)
It's like the mocked repository is not injected into the service.
Any idea what's wrong in my Kotlin code ?

Spring boot with testcontainers and jOOQ doesn't inject DSL context

I have some problem with spring boot + jOOQ and testcontainers. DSL context won't inject in my test class.
I made some preparations for using SQLContainer
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
}
Then I created some abstraction for extending my integration test classes
#ContextConfiguration(initializers = [SpringIntegrationTest.Initializer::class])
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#JooqTest
abstract class SpringIntegrationTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
inner class Initializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
with(applicationContext.environment.systemProperties) {
put("spring.datasource.url", postgreSQLContainer.jdbcUrl)
put("spring.datasource.username", postgreSQLContainer.username)
put("spring.datasource.password", postgreSQLContainer.password)
}
}
}
}
and then I implemented test class
#ExtendWith(SpringExtension::class)
class TransactionRepositoryImplTest: SpringIntegrationTest() {
#Autowired
private var dslContext: DSLContext? = null
private var transactionRepository: TransactionRepository? = null
#Before
fun setUp() {
assertThat(dslContext).isNotNull
transactionRepository = TransactionRepositoryImpl(dslContext!!)
}
#After
fun tearDown() {
}
#Test
fun findTransactionData() {
transactionRepository?.findTransactionByVehicleUuid(null).apply {
assertNull(this)
}
}
}
and when I started tests of this class - tests are fails, because of assertions are not passed.
Here is the report of tests https://pastebin.com/0HeqDcCT
So.. how it impossible? I saw a few guides with this stack(Spring/jOOQ/TestContainers). And they are all working. Maybe I missed some test dependencies? If you have experience with this case - share your solution, please. I will be very grateful.
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-jooq")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.cloud:spring-cloud-starter-consul-config")
implementation("org.springframework.cloud:spring-cloud-stream")
implementation("org.springframework.cloud:spring-cloud-stream-binder-kafka")
implementation("org.springframework.kafka:spring-kafka")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
runtimeOnly("org.postgresql:postgresql:42.2.12")
jooqGeneratorRuntime("org.postgresql:postgresql:42.2.12")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
exclude(module = "junit")
}
testImplementation("com.ninja-squad:springmockk:2.0.1")
testImplementation("org.springframework.cloud:spring-cloud-stream-test-support")
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.amqp:spring-rabbit-test")
testImplementation("org.testcontainers:postgresql:1.14.3")
}
I might be missing something but in that setup, you have to manually run
postgreSQLContainer.start() somewhere. As an example it could be done in #BeforeAll.
I found the solution. The right way was to override the start method of test containers implementation and put in system properties the credentials to container DB.
Here is the working code:
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
override fun start() {
super.start()
val container = get()
System.setProperty("DB_URL", container.jdbcUrl)
System.setProperty("DB_USERNAME", container.username)
System.setProperty("DB_PASSWORD", container.password)
}
}
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#SpringBootTest(properties = ["spring.cloud.consul.enabled = false"])
#EnableAutoConfiguration(exclude = [
RabbitAutoConfiguration::class,
KafkaAutoConfiguration::class
])
class TransactionRepositoryImplTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
#Autowired
private lateinit var dslContext: DSLContext
#Autowired
private lateinit var transactionRepository: TransactionRepository
#MockkBean
private lateinit var connectionFactory: ConnectionFactory // this is the mock for rabbit connection. U may ignore it.
#Test
fun contextLoads() {
Assertions.assertNotNull(dslContext)
Assertions.assertNotNull(transactionRepository)
}
}
and then need to fix application.yml in the tests directory
spring:
datasource:
platform: postgres
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driverClassName: org.postgresql.Driver
in java it helps me:
on test put annotation. See JooqAutoConfiguration.class
#Import({ DataSourceAutoConfiguration.class,
TransactionAutoConfiguration.class, JooqAutoConfiguration.class})

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

Resources