Note: Not a Kotest per-se question. Hi I'm new to...
# kotest
l
Note: Not a Kotest per-se question. Hi I'm new to testing. I would like to share some functions between my local unit tests in my src\test\java folder. What's the right way to do this. I know that for sharing code between unit tests and instrumented tests I can create a commonTest folder and add it to the sourceSets but in this case I only need to share code between unit tests.
m
In case you are using
Gradle
- maybe the
java-test-fixtures
plugin is something you are looking for:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures /edit: Nevermind, this is not going into the right direction.
Or .. what do you mean by
share code between my local unit tests
? I interpreted that as sharing code between tests in
src/test/kotlin
and
src/test/java
🤔
l
This is my structure. Maybe my shared code is no shared code and it belongs to one test file. I'm not exactly sure which tests belong to one test file. For example I have a parser class with a serialize/deserialize function..I guess both go into one test file right? Or should I seperate them, then I would need shared code
Copy code
class LegacyDeserializationTest : WordSpec({

    val parameterPacketHexString = """
       00 00 00 00 00 00 00 00 00 00 32 31 33....
    """.trimIndent().replace(" ","").replace("\n", "")

    val csvReaderCtx: CsvReaderContext.() -> Unit = {
        charset = Charsets.ISO_8859_1
    }

    fun readTestFile(
        fileName: String,
        ctx: CsvReaderContext.() -> Unit
    ): Sequence<Map<String, String>> {
        val file = File("src/test/resources/$fileName")
        return csvReader(ctx).parseLinesAsSequence(file.inputStream())
    }

    "legacy parser" should {
        "not throw Exception on deserialization" {
            val contents = readTestFile("/webkey-details.csv", csvReaderCtx).asSequence()
            val details = csvMapper<LegacyDetailModel> {
                ignoreUnknownFields = true
                // Actual field name of input to class field name
                customKeyMap = mutableMapOf(
                    "controlType" to "composableType",
                    "default" to "defaultValue",
                    "dataType2" to "dataType",
                    "sinceVersion2" to "sinceVersion",
                    "untilVersion2" to "untilVersion",
                    "deprecated2" to "deprecated"
                )
            }.deserialize(contents)
 
            ...
        }
Basically all of this code is needed for serialization too
w
Test code is real code too, you’re free to use all the normal tools you have in the production code. If you need a base class, file with utilities, or a dedicated class that’s not a test, you can do that just like you would in your production code
l
Ah ok so I can create a kt file with util stuff within my test folder and it's not recognized as test?
w
That’s right. You write tests according to the engine you use, for example JUnit needs a
@Test
annotation, everything else is just regular code. Of course tests should be simple so the sharing should be done sparingly (you don’t want to have to write tests for you tests)
l
Good point, I will keep it in mind. But how do I handle those cases when I have more complex tests like the one above and I have many intermediate steps that might fail? In this specific case I can't mock the
details
variable because I need real world data to test deserialization and
details
is a dependency of the
Parser.deserialize
function:
Copy code
fun deserialize(
        source: ByteArray,
        packetDetails: Sequence<LegacyDetailModel>
    ): WebKeys
w
Personally I tend to test a bit more than one class, if it makes sense. For example in your case, if
csvReader(ctx).parseLinesAsSequence(file.inputStream())
is part of another production class that’s tested separately, then I’d test it as part of the whole deserialization test. That way the only input to the tests would be a
File
(or a file name)
l
The whole csvReader functionality is already tested in
CsvParserTest
file. Did I understand you right that I should drop this file and do the testing of the parser in the deserialization test? I have another question that comes in my mind: When doing the deserialization I produce values which I need for serialization. So the serialization test would base on the deserialization test. Is this legit? And if not how can I work around this? The problem here is that I need all values from deserialization to test serialization properly, so I can't just create a map with 2 or 3 values. Another problem is that the deserialize function is wrapped into
shouldThrow
so I can't write
Copy code
shouldNotThrow<Exception> {
                webkeys = LegacyParser.deserialize(
                    source = bytes,
                    packetDetails = packetDetails
                )
            }
But I could do
Copy code
shouldNotThrow<Exception> {
                LegacyParser.deserialize(
                    source = bytes,
                    packetDetails = packetDetails
                ).also { webKeys = it }
            }
Anyway there is no guarantee that the variable is set. Should tests base on other tests? I guess no. But what options do I have otherwise?
w
Did I understand you right that I should drop this file and do the testing of the parser in the deserialization test?
That’s up to you of course, but I would consider that. I mean if I could easily create
val parser = LegacyParser(SomeDependency(), SomeOtherDependency())
then I’d do that, and test both dependencies + parser at the same time. All in all they’re clearly designed to work together, right?
When doing the deserialization I produce values which I need for serialization. So the serialization test would base on the deserialization test. Is this legit?
That’s what I usually end up doing when testing serialization, but I just do
original shouldBe deserialize(serialize(original))
. Right now your tests only check that the serialization/deserialization functionality doesn’t throw, which doesn’t say much about whether the serialization is valid. But checking if
x == deserialize(serialize(x))
may be enough, unless you expect some specific format that goes to an external service for example
l
So you put deserialization + serialization into one test?
That would make a lot of problems obsolete
What i can say for sure is that in this specific case the result of deserialization isn't equal to the result of serialization