I'm trying to use Spock and ConfigurationProperties.
But in my unit test, Mocking #ConfigurationProperties not work for me.
Property Class
#ConfigurationProperties(prefix = "jwt")
#ConstructorBinding
class JwtProperties(
val secretKey: String,
val accessTokenExp: Long,
val refreshTokenExp: Long
) {
companion object {
const val TOKEN_PREFIX = "Bearer "
const val TOKEN_HEADER_NAME = "Authorization"
const val ACCESS_VALUE = "access"
const val REFRESH_VALUE = "refresh"
}
}
Test Class
class JwtTokenProviderTest extends Specification {
private JwtProperties jwtProperties = GroovyMock(JwtProperties)
private AuthDetailsService authDetailsService = GroovyMock(AuthDetailsService)
private JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(authDetailsService, jwtProperties)
def "AuthenticateUser Success"() {
given:
jwtProperties.getSecretKey() >> "asdfdsaf"
jwtProperties.getAccessTokenExp() >> 100000
def bearerToken = jwtTokenProvider.getAccessToken("email").accessToken
def accessToken = jwtTokenProvider.parseToken(bearerToken)
authDetailsService.loadUserByUsername("email") >> new AuthDetails(new User())
when:
jwtTokenProvider.authenticateUser(accessToken)
then:
noExceptionThrown()
.
.
.
But when I run test with debug mode, JwtProperties's fields never initialized.
JwtProperties in your application is instantiated by spring. Spring will read value in the properties file and then create the instance with the required value.
In your test you don't have any spring context so nothing will create of JwtProperties for you. Furthermore you are mocking it. I think there is no point on mocking this because you just have to create the instance with the value you want.
Just do:
class JwtTokenProviderTest extends Specification {
private JwtProperties jwtProperties = JwtProperties("my-secret", 60, 120)
private AuthDetailsService authDetailsService = GroovyMock(AuthDetailsService)
private JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(authDetailsService, jwtProperties)
Related
I have this method
fun delete(id: Long) {
NotFoundExceptionValidator(!dishOfTheDayEntityRepository.existsById(id), "dishOfTheDay not found")
dishOfTheDayEntityRepository.deleteById(id)
}
NotFoundExceptionValidator this just checks if it's null then throws error
this is what I tried
#ConcurrentExecution
internal class DishOfTheDayServiceTest {
private val repo: DishOfTheDayEntityRepository = mockk()
private val mapper: DishOfTheDayMapper = mockk()
private val dishOfTheDayEntityService = DishOfTheDayService(repo, mapper)
#Test
fun `delete should work properly`() {
//given
val id: Long = 1;
//when
dishOfTheDayEntityService.delete(1)
//then
verify(exactly = 1) { repo.deleteById(1) }
}
}
when i run it it throws this error
no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
io.mockk.MockKException: no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
You forgot to mock your mocks behaviour, i.e. you should explicitly specify what the existsById() and deleteById() methods return. For example for existsById() it should look like:
every { repo.existsById(id) } returns true
I suppose that the deleteById() method returns Unit so if you don't want to do it like above you can mock DishOfTheDayEntityRepository like:
private val repo: DishOfTheDayEntityRepository = mockk(relaxUnitFun = true)
Now you don't have to mock Unit returning methods of DishOfTheDayEntityRepository. You can find more about it here.
I have an application.yml with some configuration properties required by my application.
SF:
baseurl: https://xxxxx
case:
recordTypeId: 0124a0000004Ifb
application:
recordTypeId: 0125P000000MkDa
address:
personal:
recordTypeId: 0125P000000MnuO
business:
recordTypeId: 0125P000000MnuT
I have defined a configuration class to read those properties as follows:
#Configuration
class SFProperties(
#Value("\${sf.case.recordTypeId}") val caseRecordTypeId: String,
#Value("\${sf.application.recordTypeId}") val applicationRecordTypeId: String,
#Value("\${sf.address.personal.recordTypeId}") val addressPersonalRecordTypeId:String,
#Value("\${sf.address.business.recordTypeId}") val addressBusinessRecordTypeId: String
)
The class is wired within a service without any issues,
#Service
class SFClientManagementServiceImpl( val webClientBuilder: WebClient.Builder):
ClientManagementService {
....
#Autowired
lateinit var sfProperties: SFProperties
override fun createCase(caseRequest: CaseRequestDto): Mono<CaseResponseDto> {
...
var myValue= sfProperties.caseRecordTypeId
....
}
}
When trying to test this service, I get a "lateinit property sfProperties has not been initialized" exception:
The test looks as follows:
#SpringBootTest(classes = [SFProperties::class])
class SalesforceClientManagementServiceImplTests {
#Autowired
open lateinit var sfProperties: SFProperties
#Test
fun `createCase should return case id when case is created`() {
val clientResponse: ClientResponse = ClientResponse
.create(HttpStatus.OK)
.header("Content-Type", "application/json")
.body(ObjectMapper().writeValueAsString(Fakes().GetFakeCaseResponseDto())).build()
val shortCircuitingExchangeFunction = ExchangeFunction {
Mono.just(clientResponse)
}
val webClientBuilder: WebClient.Builder = WebClient.builder().exchangeFunction(shortCircuitingExchangeFunction)
val sfClientManagementServiceImpl =
SFClientManagementServiceImpl(webClientBuilder)
var caseResponseDto =
salesforceClientManagementServiceImpl.createCase(Fakes().GetFakeCaseRequestDto())
var response = caseResponseDto.block()
if (response != null) {
assertEquals(Fakes().GetFakeCaseResponseDto().id, response.id)
}
}
I have tried many other annotations on the Test class but without success, I would appreciate any ideas.
I'm trying to get a Kotlin function to operate transactionally in Spring Boot, and I've looked at several sources for information, such as https://codete.com/blog/5-common-spring-transactional-pitfalls/ and Spring #Transaction method call by the method within the same class, does not work?. I believe I have the prerequisites necessary for the #Transactional annotation to work - the function is public and being invoked externally, if my understanding is correct. My code currently looks like this:
interface CreateExerciseInstance {
operator fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput>
}
#Component
class CreateExerciseInstanceImpl constructor(
private val exerciseInstanceRepository: ExerciseInstanceRepository, // #Repository
private val activityInstanceRepository: ActivityInstanceRepository, // #Repository
private val exerciseInstanceStepRepository: ExerciseInstanceStepRepository // #Repository
) : CreateExerciseInstance {
#Suppress("TooGenericExceptionCaught")
#Transactional
override fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput> {
...
val exerciseInstanceRecord = ... // no in-place modification of repository data
val activityInstanceRecords = ...
val exerciseInstanceStepRecords = ...
return try {
exerciseInstanceRepository.save(exerciseInstanceRecord)
activityInstanceRepository.saveAll(activityInstanceRecords)
exerciseInstanceStepRepository.saveAll(exerciseInstanceStepRecords)
Outcome.Success(...)
} catch (e: Exception) {
Outcome.Failure(...)
}
}
}
My test currently looks like this:
#ExtendWith(SpringExtension::class)
#SpringBootTest
#Transactional
class CreateExerciseInstanceTest {
#Autowired
private lateinit var exerciseInstanceRepository: ExerciseInstanceRepository
#Autowired
private lateinit var exerciseInstanceStepRepository: ExerciseInstanceStepRepository
#Autowired
private lateinit var activityInstanceRepository: ActivityInstanceRepository
#Test
fun `does not commit to exercise instance or activity repositories when exercise instance step repository throws exception`() {
... // data setup
val exerciseInstanceStepRepository = mockk<ExerciseInstanceStepRepository>()
val exception = Exception("Something went wrong")
every { exerciseInstanceStepRepository.save(any<ExerciseInstanceStepRecord>()) } throws exception
val createExerciseInstance = CreateExerciseInstanceImpl(
exerciseInstanceRepository = exerciseInstanceRepository,
activityInstanceRepository = activityInstanceRepository,
exerciseInstanceStepRepository = exerciseInstanceStepRepository
)
val outcome = createExerciseInstance(...)
assert(outcome is Outcome.Failure)
val exerciseInstances = exerciseInstanceRepository.findAll()
val activityInstances = activityInstanceRepository.findAll()
assertThat(exerciseInstances.count()).isEqualTo(0)
assertThat(activityInstances.count()).isEqualTo(0)
}
}
The test fails with:
org.opentest4j.AssertionFailedError:
Expecting:
<1>
to be equal to:
<0>
but was not.
at assertThat(exerciseInstances.count()).isEqualTo(0). Is the function actually non-public or being invoked internally? Have I missed some other prerequisite?
This test doesn't say anything about your component not being transactional.
First, you create an instance yourself rather than using the one created by Spring. So Spring knows nothing about this instance, and can't possibly warp it into a transactional proxy.
Second, the component doesn't throw any runtime exception, So Spring doesn't rollback the transaction.
I made 2 proxy object using ProxyFactory in Spring.
One proxy object used interface and one proxy object not used interface.
but not working jdk dynamic proxy. all proxy object used cglib.
The proxy object that implement interface call real method.
The proxy object that not implement interface has unexpected result.
What's the difference between two cglib proxy object?
The only difference between the two is the interface.
// Not implement interface
open class Person: AbstractPerson() {
}
abstract class AbstractPerson(var age: Int? = null,
var name: String? = null) {
fun init() {
this.age = 31
this.name = "LichKing"
}
fun introduce(): String = "age: $age name: $name"
}
// Implement interface
open class PersonImpl: AbstractPersonImpl() {
}
abstract class AbstractPersonImpl(var age: Int? = null,
var name: String? = null): PersonInterface {
fun init() {
this.age = 31
this.name = "LichKing"
}
override fun introduce(): String = "age: $age name: $name"
}
interface PersonInterface {
fun introduce(): String
}
// Test
class PersonTest {
#Test
fun implementInterface() {
val p = PersonImpl()
p.init()
val proxyFactory: ProxyFactory = ProxyFactory()
proxyFactory.setTarget(p)
val proxy = proxyFactory.proxy as PersonImpl
println(proxy.javaClass)
println(proxy.introduce()) // "age: 31 name: LichKing"
}
#Test
fun notImplementInterface() {
val p = Person()
p.init()
val proxyFactory: ProxyFactory = ProxyFactory()
proxyFactory.setTarget(p)
val proxy = proxyFactory.proxy as Person
println(proxy.javaClass)
println(proxy.introduce()) // "age: null name: null"
}
}
kotlin method's default option is final.
The cause is introduce method not be extend.
default option is open when using interface so it's could be extended.
gradle plugin kotlin-spring is only for spring annotations.
It does not work for abstract class.
Written a short convenicence extension for Testcontainers:
fun JdbcDatabaseContainer<*>.execute(query:DSLContext.()-> Query){
val connection = DriverManager.getConnection(this.getJdbcUrl(),this.getUsername(),this.getPassword())
val create = DSL.using(connection)
create.query().execute()
}
And now wanted to test it.
Flyway loads 30 entries. These should be visible in allDataPresent
canInsert inserts one entry without the extension
canInsertWithExtension does the same but via the extension function
insertMultipleWithExtension does exactly as its name implies and inserts another 5
All but the allDataPresent testcase (because that one is read-only anyway) are annotated #Transactional.
As such, I'd expect these modifications to be rolled back after the test method.
What instead happens is
[ERROR] Failures:
[ERROR] InitDataIT.allDataPresent:70
Expecting:
<36>
to be equal to:
<30>
but was not.
[ERROR] InitDataIT.canInsert:90
Expecting:
<6>
to be equal to:
<1>
but was not.
[ERROR] InitDataIT.canInsertWithExtension:112
Expecting:
<6>
to be equal to:
<1>
but was not.
Each #Test is working fine on its own. So the issue must lie with the #Transactional.
So why is that? And more importantly, how do I get the rollbacks?
Full testcase (also tried annotating the class instead, didn't make any difference):
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = [InitDataIT.TestContextInitializer::class])
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
open class InitDataIT {
companion object {
#JvmStatic
#Container
private val dbContainer = MySQLContainer<Nothing>().apply {
withDatabaseName("test")
withUsername("root")
withPassword("")
}
}
object TestContextInitializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=${dbContainer.jdbcUrl}",
"spring.datasource.username=${dbContainer.username}",
"spring.datasource.password=${dbContainer.password}",
"spring.datasource.driver-class-name=${dbContainer.driverClassName}"
).applyTo(applicationContext)
}
}
private val create:DSLContext
#Autowired
constructor(create:DSLContext){
this.create = create
}
#Test
fun allDataPresent(){
//given
val expectedNumberOfEntries = 30
val query = create.selectCount()
.from(CUSTOMERS)
//when
val numberOfEntries = query.fetchOne{it.value1()}
//then
Assertions.assertThat(numberOfEntries).isEqualTo(expectedNumberOfEntries)
}
#Test
#Transactional
open fun canInsert(){
//given
val insertquery = create.insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Tester","Alice.Tester#somewhere.tt",CustomerStatus.Contacted.name)
val expectedNumberInOffice2 = 1
//when
insertquery.execute()
//then
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
#Test
#Transactional
open fun canInsertWithExtension(){
//given
dbContainer.execute {
insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Tester","Alice.Tester#somewhere.tt",CustomerStatus.Contacted.name)
}
val expectedNumberInOffice2 = 1
//when
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
//then
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
#Test
#Transactional
open fun insertMultipleWithExtension(){
//given
dbContainer.execute {
insertInto(CUSTOMERS)
.columns(CUSTOMERS.FIRSTNAME,CUSTOMERS.LASTNAME,CUSTOMERS.EMAIL, CUSTOMERS.STATUS)
.values("Alice","Make","Alice.Make#somewhere.tt", CustomerStatus.Customer.name)
.values("Bob","Another","Bob.Another#somewhere.tt", CustomerStatus.ClosedLost.name)
.values("Charlie","Integration","Charlie.Integration#somewhere.tt",CustomerStatus.NotContacted.name)
.values("Denise","Test","Denise.Test#somewhere.tt",CustomerStatus.Customer.name)
.values("Ellie","Now","Ellie.Now#somewhere.tt",CustomerStatus.Contacted.name)
}
val expectedNumberInOffice2 = 5
//when
val numberInOffice2 = create.selectCount()
.from(CUSTOMERS)
.where(CUSTOMERS.EMAIL.contains("somewhere"))
.fetchOne{it.value1()}
//then
assertThat(numberInOffice2).isEqualTo(expectedNumberInOffice2)
}
}
The Spring #Transactional annotation doesn't just magically work with your DriverManager created JDBC connections. Your dbContainer object should operate on your spring managed data source instead.