Junit5 test suite with Kotlin - unit-testing

Based on answer in this link, created a test suite with Test classes
#RunWith(Suite::class)
#Suite.SuiteClasses(
DTOtoEntityMapperTest::class,
PostDataSourceCoroutinesTest::class,
PostDataSourceRxJava3Test::class
)
class JUnit5TestSuite
Returns error
org.junit.runners.model.InvalidTestClassError: Invalid test class 'com.x.DTOtoEntityMapperTest':
1. No runnable methods
But every test class added has test methods and runs individually for instance
class DTOtoEntityMapperTest {
private val postDTOList by lazy {
convertFromJsonToObjectList<PostDTO>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
private val postEntityList by lazy {
convertFromJsonToObjectList<PostEntity>(getResourceAsText(RESPONSE_JSON_PATH))!!
}
#Test
fun `given PostDTO is input, should return PostEntity`() {
val mapper = DTOtoEntityMapper()
// GIVEN
val expected = postEntityList
// WHEN
val actual = mapper.map(postDTOList)
// THEN
Truth.assertThat(actual).containsExactlyElementsIn(expected)
}
}

You can now run Suites purely with jUnit 5 engines:
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.Suite
#Suite
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}
You'll have to import the junit-platform-suite-engine:
https://search.maven.org/artifact/org.junit.platform/junit-platform-suite-engine
There's some docs here:
https://junit.org/junit5/docs/snapshot/user-guide/#launcher-api-engines-custom
https://junit.org/junit5/docs/snapshot/api/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html
Here's an official example:
https://github.com/junit-team/junit5/blob/main/documentation/src/test/java/example/SuiteDemo.java

For JUnit 5 I used JUnitPlatform and SelectClasses (or SelectPackages):
#RunWith(JUnitPlatform::class)
#SelectClasses(Some1Test::class, Some2Test::class)
class SuiteTest

After checking for a few days, I found correctly libs to use Junit 5 test suite with kotlin
In build.gradle, add 2 libs as below:
testImplementation ("org.junit.platform:junit-platform-suite-api:1.7.0")
testImplementation ("org.junit.platform:junit-platform-runner:1.2.0")
Then you can use test suite as below:
import org.junit.platform.runner.JUnitPlatform
import org.junit.platform.suite.api.SelectClasses
import org.junit.platform.suite.api.SelectPackages
import org.junit.runner.RunWith
#RunWith(JUnitPlatform::class)
#SelectClasses(BasicMockKUnitTest::class, HierarchicalMockKUnitTest::class)
#SelectPackages("exercise")
class TestAllSelectPackage {
}

Related

KotlinTest with Koin: InvocationTargetException

I'm unable to use Koin 2.0.1 with Kotlin-test 3.4.2. I get an InvocationTargetException like this:
Running koinexample.KoinSampleTests
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.009 sec <<< FAILURE! - in koinexample.KoinSampleTests
koinexample.KoinSampleTests Time elapsed: 0.009 sec <<< ERROR!
java.lang.reflect.InvocationTargetException
at koinexample.KoinSampleTests.getKoin(KoinSampleTests.kt:26)
at koinexample.KoinSampleTests.<init>(KoinSampleTests.kt:61)
I've created a small example on GitHub that reproduces this error:
https://github.com/elifarley/kotlin-tests-with-koin-examples
Just execute these commands to clone the repo and run tests:
git clone https://github.com/elifarley/kotlin-tests-with-koin-examples.git
cd kotlin-tests-with-koin-examples
mvn
Here's the main Kotlin file:
package koinexample
import io.kotlintest.koin.KoinListener
import io.kotlintest.shouldBe
import io.kotlintest.specs.FreeSpec
import org.koin.core.inject
import org.koin.dsl.module
import org.koin.test.KoinTest
data class Stats(var ok: Long = 0, var error: Long = 0)
interface StatsServer {
fun newError(): Long
}
class StatsServerSimple(private val stats: Stats) : StatsServer {
override fun newError() = stats.error++
}
val appModule = module {
single { Stats() }
single { StatsServerSimple(get()) as StatsServer }
}
class KoinSampleTests : FreeSpec(), KoinTest {
private val modules = listOf(
appModule
)
override fun listeners() = listOf(KoinListener(modules))
val statsServer: StatsServer by inject()
init {
"Happy path" {
statsServer.newError() shouldBe 1
}
}
}
Your issue seems to be a simple import confusion.
Note that you're using import org.koin.core.inject, which is this function:
inline fun <reified T> KoinComponent.inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> =
getKoin().inject(qualifier, parameters)
It needs getKoin to work, and therefore you cannot initialize your test (The test class is initialized before Koin had a chance to start with the listener).
The correct import is import org.koin.test.inject, which translates to:
inline fun <reified T> KoinTest.inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> = lazy { get<T>(qualifier, parameters) }
Take note that this is indeed lazy, so Kotest will have a chance to initialize Koin before your tests start.
Fixing that import should resolve this issue
Seems like you're never starting the Koin app. You need to have
startKoin {
modules(appModule)
}
in your test method or in the beforeSpec/beforeTest function call of the spec.
Or use something like here:
override fun listeners() = listOf(KoinListener(appModule))
which is described in the documentation for kotlintest/kotest: https://github.com/Kotest/Kotest/blob/master/doc/extensions.md#koin

How do I import a class in groovy when package and folder deviate?

I am writing groovy scripts for jenkins which defines my path names to some extent. I did try to reasearch the problem on stackoverflow and google and I could not figure out a good solution.
Here is a minimal example:
File Structure:
runTests.bat
vars/SemVer.groovy
tests/SemVerTests.groovy
The batch file executes my unit tests:
docker run --rm -v %cd%:/home/groovy/scripts -w /home/groovy/scripts groovy groovy tests/SemVerTests.groovy
SemVer.groovy
#!/usr/bin/env groovy
package vars
class SemVer {
private String _original
SemVer(String original) { this._original = original }
String toString() { return "${this._original}" }
}
SemVerTests.groovy
import groovy.util.GroovyTestSuite
import junit.framework.Test
import junit.textui.TestRunner
import vars.SemVer
class GetLatestSemVerShouldConstruct extends GroovyTestCase {
void testDisplay() {
def actual = new SemVer("Hello World!").toString()
assertToString(actual, "Hello World!")
} }
class AllTests {
static Test suite() {
def allTests = new GroovyTestSuite()
allTests.addTestSuite(GetLatestSemVerShouldConstruct.class)
return allTests
} }
TestRunner.run(AllTests.suite())
The problem is that I need to use SemVer.groovy in my jenkins scripts that live in vars/. To do that I assume I need to remove the line package vars.
When I do that I get:
Compilation incomplete: expected to find the class vars.SemVer in file:/home/groovy/scripts/vars/SemVer.groovy, but the file contains the classes: SemVer
How can I import the class to tests/SemVerTests.groovy without defining the package in SemVer?
Add vars to the classpath (see below), remove the package from SemVers and just import SemVers in your test.
$ find .
.
./tests
./tests/SemVerTests.groovy
./vars
./vars/SemVer.groovy
$ head -n 4 vars/SemVer.groovy
class SemVer {
private String _original
SemVer(String original) { this._original = original }
String toString() { return "${this._original}" }
$ head -n 8 tests/SemVerTests.groovy
import groovy.util.GroovyTestSuite
import junit.framework.Test
import junit.textui.TestRunner
import SemVer
class GetLatestSemVerShouldConstruct extends GroovyTestCase {
void testDisplay() {
def actual = new SemVer("Hello World!").toString()
$ groovy -cp vars tests/SemVerTests.groovy
.
Time: 0.035
OK (1 test)

sbt doesn't recognize ScalaTest Table-driven property checks as tests

I wrote some ScalaTest Table-driven property checks and I'm trying to run them with sbt test. Looking at the report I see that ScalaTest can recognize all the JUnit tests I have (they are in the same class as the checks), it runs property checks (i.e., forAll body), but it doesn't treat forAll as a test. If it fails I see the stack trace in the report (with ScalaTest failed test exception) and sbt says there was an "error" during the test run, but it says that all of the tests passed. The total number of tests in the report includes only JUnit tests.
Is there the support for this style of tests in sbt?
forAll in PropertyChecks is not a test. It is essentially a glorified assertion. You need to place assertions inside named tests. How to do that depends on your chosen style. For example, in FunSuite, you'd write something like:
class MySpec extends FunSuite with PropertyChecks {
test("give the test a name here") {
forAll(x: Int, y: Int) {
// make assertions here
}
}
}
Instead of calling forAll, make the test class extend from org.scalatest.prop.Checkers and then in each test, call check with the Property to be tested. In this case, "Property" probably means the forAll that you've created.
So I'm going to guess that currently you have a test class that looks like:
class ExampleSuite extends AssertionsForJUnit {
val fractions = Table(
("n", "d"),
( 1, 2),
///...
)
forAll (fractions) { (n: Int, d: Int) => // ...
#Test def verifySomethingElse = ???
}
I believe what you need to do is extend from Checkers and move your forAll into a test.
class ExampleSuite extends AssertionsForJUnit with org.scalatest.prop.Checkers {
#Test def verifyFractions = {
val fractions = Table(
("n", "d"),
( 1, 2),
///...
)
check(forAll (fractions) { (n: Int, d: Int) => ???)
}
#Test def verifySomethingElse = ???
}
The standard way is to create a FunSuite test with Matchers and TableDrivenPropertyCheck
Example:
import org.scalatest._
import org.scalatest.prop.TableDrivenPropertyChecks._
class CreateSpec extends FunSuite with Matchers {
test("B-Tree-Create for different degree parameter value") {
val params = Table(("degree", "result"),
(0, Tree(Leaf(), 0)),
(2, Tree(Leaf(), 1)),
(1999, Tree(Leaf(), 1999)))
forAll(params) {(degree, result) => Algorithms.create(degree) == result}
}
}

Grails metaclass modification not reliable within unit tests

In a grails 2 project I'm using groovy's metaclass programming to add some methods to my domain classes.
Everything is working fine at runtime and I can run my integration tests fine.
But for unit tests I have some issues.
I have created a test mixin that is in charge of initializing the metaclass programming part.
This mixin is not running reliably:
the methods added to the metaclass are not available, or they are available after a first call, or they are available only after a previous grails test-app unit: command has been called.
This is quite a problem for continuous build.
You should be able to reproduce this issue (at least with grails 2.0.4) by
0) create a new grails projects
1) add a domain object
create-domain-class playground.Data
2) add this class to your src/groovy/playground dir
package playground
import grails.test.mixin.domain.DomainClassUnitTestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.codehaus.groovy.grails.commons.GrailsDomainClass
import org.junit.Before
class EnhanceDomainTestMixin {
boolean enhancerMethodCalled = false;
GrailsApplication application
MetaMethod mockDomainMethod
//replace the mockDomain Method from DomainClassUnitTestMixin with this closure
def enhancedMockDomain = { Class cl, List list ->
def enhanced =cl.metaClass.getMetaMethod("isEnhanced")
try {
//run the mockDomain method to have the mocked domain class registered in the grails application
mockDomainMethod.invoke(delegate, cl, list)
}
finally {
//enhance the grails domain with a new method
def domain = application.getDomainClass(cl.name) as GrailsDomainClass
domain.metaClass.isEnhanced = { return true; }
assert domain.newInstance().isEnhanced();
}
}
#Before void runDomainEnhancer() {
enhancerMethodCalled = true;
//GrailsUnitTestMixin.initGrailsApplication() should have already been called. (at least this was not an issue here)
application = GrailsUnitTestMixin.grailsApplication
//pick the mockDomain method
mockDomainMethod = DomainClassUnitTestMixin.metaClass.pickMethod("mockDomain", Class, List)
//if the picked mockDomain has never been enhanced, wrap it.
if(mockDomainMethod != enhancedMockDomain) {
DomainClassUnitTestMixin.metaClass.mockDomain = enhancedMockDomain
}
}
}
3) Add this small utils class (in test/unit/playground)
package playground
class TestSetup {
static Data d1
static void setup() {
d1 = new Data()
assert d1.isEnhanced()
}
}
4) Add these tests into the unit test already created by grails DataTests
package playground
import grails.test.mixin.*
#TestFor(Data)
#TestMixin(EnhanceDomainTestMixin)
class DataTests {
void testIsEnhancedLocal() {
assert enhancerMethodCalled
Data d = new Data()
assert d.isEnhanced()
}
void testIsEnhancedLocalSecondTime() {
assert enhancerMethodCalled
Data d = new Data()
assert d.isEnhanced()
}
void testIsEnhancedGlobalFirstTime() {
assert enhancerMethodCalled
TestSetup.setup()
assert TestSetup.d1 != null
}
void testIsEnhancedGlobalSecondTime() {
assert enhancerMethodCalled
TestSetup.setup()
assert TestSetup.d1 != null
}
}
Now run this command:
grails test-app unit:
you should have something like this output:
| Completed 4 unit tests, 4 failed in 1651ms
| Tests FAILED - view reports in target\test-reports
Now run the this command again (sometime one more is needed):
grails test-app unit: playground.DataTests
testMixin> grails test-app unit: playground.DataTests
| Completed 4 unit tests, 0 failed in 1384ms
| Tests PASSED - view reports in target\test-reports
So does anyone has a clue of why the metaClass modification is not reliable while running unit tests ? And how to workaround this issue ?
I had to use grailsApplication config in my domain class method. I ran into the same problem. Try using Holders.config instead of grailsApplication.config. It worked for me.

Second Use of GrailsApplication Mock in Service Test Fails

I'm unit testing a Grails service and using Mocks to mock out calls to the
GrailsApplication class. I have one test that succeeds but when I try
subsequent tests they fail. I am using demand to mock the isDomainClass
method. I have tried copying and pasting the code from the test that
succeeds to the test method that fails but the second time the same code
runs it fails saying that no more calls to isDomainClass are expected. I'm
suspecting some leakage between the methods but I can't see where it is.
Things I've tried already:
Running the tests from the command line (I'm running the tests under SpringSource Tool Suite version 2.7.0.201105292341-M2.)
Moving the failing test to a different test class (the test that runs first succeeds)
Changing the number range in the demands clause to 1..5 (second test still fails)
Here is the relevant portions of my test case:
package simulation
import grails.test.*
import org.joda.time.*
import org.codehaus.groovy.grails.commons.GrailsApplication
class ObjectSerializationServiceTests extends GrailsUnitTestCase {
def objectSerializationService
protected void setUp() {
super.setUp()
objectSerializationService = new ObjectSerializationService()
}
protected void tearDown() {
super.tearDown()
objectSerializationService = null
}
void testDomainObjectSerialization() {
def otherControl = mockFor(GrailsApplication)
otherControl.demand.isDomainClass(1..1) {true}
otherControl.demand.getDomainClass(1..1) {className ->
assert className == "simulation.TestDomainClass"
TestDomainClass.class
}
objectSerializationService.grailsApplication = otherControl.createMock()
def now = new DateTime()
def testObject = new TestDomainClass([id:57, someOtherData:"Some Other
Data", theTime:now])
def testInstances = [testObject]
mockDomain(TestDomainClass, testInstances)
def serialized = objectSerializationService.serializeObject(testObject)
def deserialized =
objectSerializationService.deserializeObject(serialized)
assert deserialized == testObject
assert serialized.objectType == SerializedObject.ObjectType.DOMAIN
otherControl.verify()
}
void testSerializableSerialization() {
def otherControl = mockFor(GrailsApplication)
otherControl.demand.isDomainClass(1..1) {true}
otherControl.demand.getDomainClass(1..1) {className ->
assert className == "simulation.TestDomainClass"
TestDomainClass.class
}
objectSerializationService.grailsApplication = otherControl.createMock()
def now = new DateTime()
def testObject = new TestDomainClass([id:57, someOtherData:"Some Other
Data", theTime:now])
def testInstances = [testObject]
mockDomain(TestDomainClass, testInstances)
def serialized = objectSerializationService.serializeObject(testObject)
def deserialized =
objectSerializationService.deserializeObject(serialized)
assert deserialized == testObject
assert serialized.objectType == SerializedObject.ObjectType.DOMAIN
otherControl.verify()
}
}
And the output:
Testcase: testDomainObjectSerialization took 0.943 sec
Testcase: testSerializableSerialization took 0.072 sec
FAILED
junit.framework.AssertionFailedError: No more calls to 'isDomainClass'
expected at this point. End of demands.
at grails.test.MockClosureProxy.doBeforeCall(MockClosureProxy.java:66)
at grails.test.AbstractClosureProxy.call(AbstractClosureProxy.java:74)
at
simulation.ObjectSerializationService.serializeObject(ObjectSerializationService.groovy:20)
at simulation.ObjectSerializationService$serializeObject.call(Unknown
Source)
at
simulation.ObjectSerializationServiceTests.testSerializableSerialization(ObjectSerializationServiceTests.groovy:68)
I got a similar error trying to use mockFor on jms Message interface in multiple test cases.
I got around it by creating a custom interface that extends from the interface that needs to be mocked. You would use the custom interface to create the mock.
e.g.
private interface GrailsApplicationTest1 extends GrailsApplication(){}
testOne(){
def control = mockFor(GrailsApplicationTest1)
//...rest of code
}
private interface GrailsApplicationTest2 extends GrailsApplication(){}
testTwo(){
def control = mockFor(GrailsApplicationTest2)
//...rest of code
}
//add more private interfaces for additional test cases..
I'm not exactly sure why but I think the mockFor behaves differently between interfaces and non-interfaces. But that's just a wild guess.