Philipp Mayer
12/07/2021, 5:01 PMclass SomeE2ETest: E2ETestEnvironment { ... }
where E2ETestEnvironment is a class (annotated with @SpringBootTest
) that holds common project config, e.g. a preconfigured http client to post something to the api.
Converting this test simply to a Kotest test will not work as we can’t do the following:
class SomeE2EKotest: E2ETestEnvironment(), BehaviorSpec({ ... })
because only one class may appear in a super type list.
Long story short: I unfortunately couldn’t find any resource about how to do this with Kotest. Our main scenario is that we probably (currently) only translate the E2E Tests, so I’d like to find a way to access the E2ETestEnvironment
from both JUnit tests and Kotest Tests.
Thanks in advance!sam
12/07/2021, 5:08 PMEmil Kantis
12/07/2021, 10:32 PME2ETestEnvironment
an interface) is to extend the spec.. In this case, something like:
@SomeAnnotations
class EndToEndSpec(spec: BehaviorSpec() -> Unit): BehaviorSpec(
{
listeners(SomeListener)
setup()
spec()
teardown()
}
)
etc..sam
12/07/2021, 10:36 PMthanksforallthefish
12/08/2021, 6:55 AM@Transactional
@AutoConfigureMockMvc
@SpringBootTest(
classes = [TestConfigs...],
properties = ["spring.main.allow-bean-definition-overriding=true"],
)
annotation class ResourceIntegrationTest
various test configs defines beans to help with test setups and executions (though admittedly then our spring test config is different than production, but then those bean can be injected in your tests) as well as replacing some bean with stubs (for instance when we depend on external APIs)Philipp Mayer
12/08/2021, 7:55 AM@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = ["some properties"]
)
@ActiveProfiles("test")
@ContextConfiguration(initializers = [SomeApiInitializer::class])
@Import(AlwaysAuthorizedClientManager::class)
class E2ETestEnvironment(override val webClient: WebTestClient) {
@Autowired
lateinit var someApi: WireMockServer
@Autowired
lateinit var dealRepository: DealRepository
@Autowired
lateinit var objectMapper: ObjectMapper
@Autowired
lateinit var publisher: PubSubPublisherTemplate
@Autowired
lateinit var subscriberTemplate: PubSubSubscriberTemplate
val ourOwnApi = OurOwnApi(webClient)
}
fun E2ETestEnvironment.resetState() {
resetDb()
resetPubSub()
resetApis()
}
fun E2ETestEnvironment.save(deal: Deal): Deal = dealRepository.save(deal).block()!!
fun E2ETestEnvironment.pullPublishedPubSubMessages(
topic: String,
size: Int,
): List<PubsubMessage> = await.atMost(20, TimeUnit.SECONDS).until(...)
There’s quite some ugly code in there (look at all the lateinit vars. 🤮) that needs to be refactored anyway, but the main idea is to have the same configured spring context for the E2E Tests while also abstracting away clean up commands + querying data from external sources (db, pubsub).
It would be really interesting how you guys are usually doing this - I see lots of room for improbement on my side.
Anyway: Looking into the code I see that the E2E TestEnvironment is ofc only used by the E2E Tests (the ones that I want to move to Kotest), so still having interopability with JUnit is not a factor.
@Emil Kantis Extending the Spec is for sure interesting, I’ll look into that!
@thanksforallthefish also an interesting approach - our main problem here is that we also use properties from our Environment, so an annotation is not feasable.thanksforallthefish
12/08/2021, 8:16 AMresetState
you can look at kotest extensions, that way you can run it automatically before/after each test, spec (which in my head is the file) etc, as well as on demand (I guess, never really tried)
for your autowired stuff and the client you can create a new test bean and inject those into your test (going composition over inheritance).
this new bean can also have the methods save
and pullPublishedPubSubMessages
. eg:
@Component
class TestBean(
autowired stuff here
) {
val ourOwnApi = OurOwnApi(webClient)
fun save(deal: Deal): Deal = dealRepository.save(deal).block()!!
fun pullPublishedPubSubMessages(
topic: String,
size: Int,
): List<PubsubMessage> = await.atMost(20, TimeUnit.SECONDS).until(...)
//could also have reset here, I just assumed you want to clean before/after tests, not in the middle, and for that I like extensions as it is transparent
}