I've implemented a TestListener as follows:
object IntegrationTest: TestListener {
override fun beforeProject() {
println("integration tests - beforeProject")
}
override fun beforeSpec(description: Description, spec: Spec) {
println("integration tests - beforeSpec")
}
}
And used it in a test:
class SimpleTest: StringSpec() {
override fun listeners() = listOf(IntegrationTest)
init {
"it - 1" {
println("it - 1")
}
"it - 2" {
println("it - 2")
}
}
}
The problem is that integration tests - beforeProject is never printed in the output.
The result is:
integration tests - beforeSpec
it - 1
it - 2
I tried it in intellij and using gradle CLI. Am I missing something?
beforeProject has to run before any tests are discovered, otherwise it's not really before the project but would kind of be "before any tests have executed" (The difference might not be important in your use class, but KotlinTest mantains the distinction).
Therefore overriding that method in a listener that's added to a test class doesn't do anything (as you have seen).
So instead you need to add your listener to ProjectConfig which is project wide configuration. You do this by subclassing AbstractProjectConfig and putting it in a special package name, like this:
package io.kotlintest.provided
object ProjectConfig : AbstractProjectConfig() {
// add listeners here
}
See full docs here:
https://github.com/kotlintest/kotlintest/blob/master/doc/reference.md#project-config
Related
In the kotest framework, there is a way to group tests with custom tags and you can run the particular group by selecting via Gradle parameter like gradle test -Dkotest.tags="TestGroupOne"
I have two test cases one is with a tag and another one is without a tag
object Linux : Tag()
class MyTests : StringSpec({
"without tag" {
"hello".length shouldBe 5
}
"with tag".config(tags = setOf(Linux)) {
"world" should startWith("wo2")
}
})
Now if I run gradle build it runs both tests, but I would like to run the tests which are not tagged by default. In the above example, the test without tag should run if there is no parameter passed in gradle
One way to achieve this behaviour is by adding a task in build.gradle.kts file
val test by tasks.getting(Test::class) {
systemProperties = System.getProperties()
.toList()
.associate { it.first.toString() to it.second }
if(!systemProperties.containsKey("kotest.tags"))
systemProperties["kotest.tags"] = "!Linux"
}
As you can see, when there is no parameter passed for -Dkotest.tags I'm manually adding the value !Linux to the systemProperties so that the build script will run tests which are not tagged by default.
Question: Is there any better way to achieve this?
I even tried adding systemProp.gradle.kotest.tags="!Linux" in gradle.properties file but there is no effect.
Your solution is not very robust in the sense that you depend on the concrete tag that is used. It seems that there is no easier solution for that, because the syntax for tag expressions does not allow to write something like "!any".
However, it is possible to write a Kotest extension for what you need that looks like this:
import io.kotest.core.TagExpression
import io.kotest.core.config.ProjectConfiguration
import io.kotest.core.extensions.ProjectExtension
import io.kotest.core.extensions.TestCaseExtension
import io.kotest.core.project.ProjectContext
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import io.kotest.engine.tags.runtimeTags
object NoTagsExtension : TestCaseExtension, ProjectExtension {
private lateinit var config: ProjectConfiguration
override suspend fun interceptProject(context: ProjectContext, callback: suspend (ProjectContext) -> Unit) {
config = context.configuration
callback(context)
}
override suspend fun intercept(testCase: TestCase, execute: suspend (TestCase) -> TestResult): TestResult {
return if (config.runtimeTags().expression == TagExpression.Empty.expression) {
if (testCase.spec.tags().isEmpty() && testCase.config.tags.isEmpty()) {
execute(testCase)
} else TestResult.Ignored("currently running only tests without tags")
} else execute(testCase)
}
}
The first function interceptProject is just there to obtain the project configuration in order to determine the specified set of tags for the current test run.
The second function intercept is for each test-case. There we determine if any tags have been specified. If no tags were specified (i.e. we have an empty tag expression), we skip all test where any tag has been configured at the spec or test-case. Otherwise, we execute the test normally, and it will then possibly ignored by Kotlin's built-in mechanisms, depending on its tags.
The extension can be activated project-wide in the ProjectConfig:
class ProjectConfig : AbstractProjectConfig() {
override fun extensions(): List<Extension> = super.extensions() + NoTagsExtension
}
Now, with the extension in place, only tests without tag run by default, regardless of what tags you use in your project.
I have the following project, still in development: https://github.com/TarekSaid/blotit, using Kotlin and Spring Webflux.
I was writing my unit tests with Spock (Groovy), but after some issues with testing Kotlin Coroutines and being unable to use syntactic sugar with data classes (even with #JvmOverloads), I've decided to switch to Kotest + Mockk.
My only issue now is with the handler unit tests' performance, as I have to use mockkstatic on ServerRequestExtensionsKt to mock request.awaitBodyOrNull. While the Spock specification runs in 0.072s, the equivalent Kotest test runs in 0.440s. While negligible, it could add up as I add more tests.
I was wondering if there was a better way to unit test the handler with Kotest (please note that I already use WebTestClient to run integration tests). I'll eventually add some verifications to check for service calls, etc, which will be mocked. That's why I'm testing the handler directly.
The handler itself is still very simple:
#Component
class RatingHandler {
suspend fun rate(request: ServerRequest): ServerResponse {
// DataSheet is a data class used as the request body
return request.awaitBodyOrNull(DataSheet::class)?.let {
ServerResponse.ok().buildAndAwait()
} ?: ServerResponse.badRequest().buildAndAwait()
}
}
Here's my test class:
class RatingHandlerTest : StringSpec({
val handler = RatingHandler()
val request: ServerRequest = mockk()
val sheet: DataSheet = mockk()
// tried to use beforeSpec to see if it'd make a difference
beforeSpec {
mockkStatic("org.springframework.web.reactive.function.server.ServerRequestExtensionsKt")
}
"rate should return status ok when the body is present" {
coEvery { request.awaitBodyOrNull(DataSheet::class) } returns sheet
handler.rate(request).statusCode() shouldBe HttpStatus.OK
}
"rate should return invalid request for missing sheet" {
coEvery { request.awaitBodyOrNull(DataSheet::class) } returns null
handler.rate(request).statusCode() shouldBe HttpStatus.BAD_REQUEST
}
})
Is that how I'm supposed to unit test the handler, or is there a better way?
Given some simple content:
#Composable
fun MyContent() {
var showThing by remember { mutableStateOf(false) }
if (showThing) {
Box(Modifier.testTag("thing")) {
Text("The Thing")
}
}
}
If I try to test whether the thing has been displayed:
#OptIn(ExperimentalTestApi::class)
class Scratch {
#get:Rule
val compose = createComposeRule()
#Test
fun test() {
runBlocking(Dispatchers.Main) {
compose.setContent {
MyContent()
}
compose.awaitIdle()
compose.onNodeWithTag("thing").assertIsNotDisplayed()
}
}
}
I get this:
An operation is not implemented.
kotlin.NotImplementedError: An operation is not implemented.
at androidx.compose.ui.test.DesktopAssertions_desktopKt.checkIsDisplayed(DesktopAssertions.desktop.kt:23)
at androidx.compose.ui.test.AssertionsKt.assertIsNotDisplayed(Assertions.kt:49)
at Scratch$test$1.invokeSuspend(Scratch.kt:44)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
...
I thought testing whether something was displayed or not would be the most basic thing to test, but it isn't supported by the framework yet. The test framework is experimental, so I was expecting to find things missing, but not like this.
Is there another way to do this which I'm missing? All the tutorials out there talk about assertIsDisplayed() being the way, but maybe there is an alternative?
It's not a direct substitute, but unfortunately, JB Compose Desktop has these limitations in the UI test suite. Aside from using only JUnit 4, and not being compatible with the newer version, many assertion methods and also screen interaction methods are not implemented, such as the .assertIsNotDisplayed() that you tried to use, and also actions like .performTextInput().
An alternative for your problem would be using other methods like .assertDoesNotExist() and .assertExists().
It's not going to tell you if the element is in the boundaries of the screen and visible, but at least will tell you that your node exists and is instantiated, which is something, and it's better than nothing.
Until JetBrains implement the complete desktop test suite, we need to work with what we have, or maybe try implementing some things as a workaround.
In your case, this will work:
#OptIn(ExperimentalTestApi::class)
class Scratch {
#get:Rule
val compose = createComposeRule()
#Test
fun test() {
runBlocking(Dispatchers.Main) {
compose.setContent {
MyContent()
}
compose.awaitIdle()
compose.onNodeWithTag("thing").assertDoesNotExist()
}
}
I need to call the OpenJPA PCEnhancerTask class from Kotlin instead of Groovy. The following code works just fine (based on a previous solution documented here):
def openJPAClosure = {
def entityFiles = sourceSets.main.output.classesDirs.asFileTree.matching {
include 'com/company/persist/*Entity.class'
}
println "Enhancing with OpenJPA:"
entityFiles.getFiles().each {
println it
}
ant.taskdef(
name : 'openjpac',
classpath : sourceSets.main.runtimeClasspath.asPath,
classname : 'org.apache.openjpa.ant.PCEnhancerTask'
)
ant.openjpac(
classpath: sourceSets.main.runtimeClasspath.asPath,
addDefaultConstructor: false,
enforcePropertyRestrictions: true) {
entityFiles.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
}
}
I was looking at the documentation on how to call Ant tasks from Gradle but I could not translate all the necessary steps using the GroovyBuilder. So instead I tough of calling the PCEnhancer directly:
fun openJPAEnrich() {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.getFiles().forEach() {
println(it)
}
org.apache.openjpa.ant.PCEnhancerTask.main(asList(entityFiles))
}
But it complains about not being able to find org.apache.openjpa in the classpath (but is it listed as a compilation dependency)
My questions are:
What is the correct way to translate the original Groovy construct to Kotlin using groovyBuilder
If is not possible, how you can correctly call PCEnhancer from Kotlin in Gradle?
So I ended making it work with a custom JavaExec Gradle task:
tasks.create<JavaExec>("openJPAEnrich") {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.files.forEach() {
println(it)
}
classpath = sourceSets.main.get().runtimeClasspath
main = "org.apache.openjpa.enhance.PCEnhancer"
args(listOf("-enforcePropertyRestrictions", "true", "-addDefaultConstructor", "false"))
entityFiles.forEach { classFile -> args?.add(classFile.toString())}
}
I was tempted to build my own custom Gradle task but for this felt overkill.
Thanks.
--Jose
I'm trying to make a proper project, like the one setup when you go through the Kotlin wizard or something, but I can't figure it out.
class SpigotKtWizard : ModuleBuilder() {
override fun setupRootModel(modifiableRootModel: ModifiableRootModel?) {}
override fun getModuleType(): ModuleType<*> {
return SpigotKtModuleType.instance
}
override fun createWizardSteps(wizardContext: WizardContext, modulesProvider: ModulesProvider): Array<ModuleWizardStep> {
return arrayOf(BuildOptionsStep())
}
override fun createProject(name: String?, path: String?): Project? {
val project = super.createProject(name, path) ?: return null
val d = project.baseDir.createChildData(this, "Test")
File(d.path).writeText("Testing boyyyy")
return project
}
}
This is what I have currently, and I'm getting this:
But other projects (and specifically, the same design I'm trying to achieve), look more like this:
Is there a page in the docs that I missed?
In pic 2 you're displaying a "project" instead of a "module", see the offical doc.
You may want to implement a DirectoryProjectGeneratorBase<YourSettingsBean> and register a (as an example you may refer to this file my julia plugin) directoryProjectGenerator in plugin.xml.
In your implementation of generateProject, you can create files, set files as source root/test root/excluded root by using:
ApplicationManager.getApplication().runWriteAction {
val modifiableModel: ModifiableRootModel = ModifiableModelsProvider.SERVICE.getInstance().getModuleModifiableModel(module)
module.rootManager.modifiableModel.apply {
inheritSdk()
contentEntries.firstOrNull()?.apply {
addExcludeFolder(findOrCreate(baseDir, "out", module))
addSourceFolder(findOrCreate(baseDir, "src", module), false)
}
commit()
}
ModifiableModelsProvider.SERVICE.getInstance().commitModuleModifiableModel(modifiableModel)
}
This should be missing in the doc, BTW. So it's not your fault. But I recommend you to take a look at the existing plugin projects (like the julia plugin mentioned above, the mathemetica plugin or the covscript plugin), which are extremely helpful for new comers.