I want to have a common fixture for my tests:
#RunWith(JUnitPlatform::class)
abstract class BaseSpek: Spek({
beforeGroup {println("before")}
afterGroup {println("after")}
})
and now I want to use that spec:
class MySpek: BaseSpek({
it("should xxx") {}
})
but i got compilation error due to no-arg BaseSpek constructor. what's the correct way of achieving what i need?
You can define an extension on Spec that sets up the desired fixture and then apply it in your Speks as follows:
fun Spec.setUpFixture() {
beforeEachTest { println("before") }
afterEachTest { println("after") }
}
#RunWith(JUnitPlatform::class)
class MySpek : Spek({
setUpFixture()
it("should xxx") { println("xxx") }
})
Though this is not exactly what you asked, it still allows flexible code reuse.
UPD: This is a working option with Speks inheritance:
open class BaseSpek(spec: Spec.() -> Unit) : Spek({
beforeEachTest { println("before") }
afterEachTest { println("after") }
spec()
})
#RunWith(JUnitPlatform::class)
class MySpek : BaseSpek({
it("should xxx") { println("xxx") }
})
Basically, do do this, you invert the inheritance direction, so that the child MySpek passes its setup in the form of Spec.() -> Unit to the parent BaseSpek, which adds the setup to what it passes to Spek.
Related
Update While searching for the optimal solution, I switched temporarily to an approach using a dedicated test factory that performs the init in combination with forAll. In this way, I don't have to repeat the init code in every test factory.
fun somePreStuffFactory() = funSpec {
beforeTest {
println("Do some init stuff here")
}
test("dummy test just to trigger beforeTest") {}
}
class MyTest : FunSpec({
runBlocking {
forAll(
row(MyTestFactory::someTest1),
row(MyTestFactory::someTest2)
) { testFactoryFunc ->
include(somePreStuffFactory())
include(testFactoryFunc())
}
}
})
End of update
I have a lot of tests inside test factories. Before running the tests inside a test factory, I need to do some initial setup in the beginning of each test factory, and the setup is the same for all test factories.
Problem is that beforeSpec is not invoked inside test factory, hence I'm currently using a dirty workaround by doing init stuff in the first test in each test factory. I would highly appreciate any advice on this.
The optimal solution would be to have a life-cycle hook inside the the test class that runs before each test factory.
Code to reproduce
class MyTest : FunSpec({
include(someTest1())
include(someTest2())
})
import io.kotest.core.spec.style.funSpec
object MyTestFactory {
fun someTest1() = funSpec {
beforeSpec {
/** Not invoked */
println("Hello from someTest1#beforeSpec")
}
test("Init stuff is done inside a test") {
/** some init here */
}
test("first test") {
println("Hello from first test")
}
test("second test") {
println("Hello from second test")
}
}
fun someTest2() = funSpec {
beforeSpec {
/** Not invoked */
println("Hello from someTest2#beforeSpec")
}
test("Init stuff is done inside a test") {
/** some init here */
}
test("third test") {
println("Hello from third test")
}
}
}
What I've tried so far
After bumping Kotest from 4.6.4 to 5.4.2, I was able to run the code in answer from #ocos. Problem is that BeforeSpecSample#beforeSpec is invoked just once, not for each test factory which is my requirement.
object BeforeSpecSample : BeforeSpecListener {
override suspend fun beforeSpec(spec: Spec) {
println("Hello from beforeSpec")
}
}
class MyTest : FunSpec({
extensions(BeforeSpecSample)
include(someTest1())
include(someTest2())
})
Update
After reading this GitHub issue, I successfully tested the following approach using a boolean var initialized andbeforeTest. It would have been nice if this approach could be used inside test class instead of inside each test factory, but without any life-cycle hooks for test factories, I don't see how that can be done.
fun someTest1() = funSpec {
var initialized = false
beforeTest {
if (!initialized) {
println("Hello from someTest1#beforeTest")
initialized = true
}
}
/** tests goes here */
}
Environment
Kotest 4.6.4, Kotlin 1.7.10, Micronaut 3.6.3
Kotest test factory doc
If you don't mind grouping everything inside a test factory in a context block, you could use a BeforeContainerListener for initialization:
object InitExtension : BeforeContainerListener {
override suspend fun beforeContainer(testCase: TestCase) {
if(testCase.parent == null) {
println("init stuff")
}
}
}
The if(testCase.parent == null) is just there to allow your factory to have other nested contexts that will not trigger an additional invocation of the initialization.
Then you can write your factory like this:
fun someTest1() = funSpec {
extension(InitExtension)
context("someTest1") {
test("first test") {
println("Hello from first test")
}
test("second test") {
println("Hello from second test")
}
}
}
The initialization in InitExtension will be called at the beginning of context someTest1, and can analogously be included in other test factories that can each be initialized by the same extension.
include is using only tests and extensions defined in your someTest()'s TestFactory. This is why beforeSpec not invoked.
You can register an extension in your MyTest. Here is an example.
// you may use object MyTestFactory : BeforeSpecListener
object BeforeSpecSample : io.kotest.core.listeners.BeforeSpecListener {
override suspend fun beforeSpec(spec: Spec) {
println("-> before spec <-")
}
}
class MyTest : FunSpec({
extensions(BeforeSpecSample)
include(someTest())
test("my test 1") { println("my test 1") }
})
I have two class TestTarget, MockTarget and test code below. If the TestTarget has two functions with same name and same count of parameter, the any() is ambiguous. I need to assign the type to any(ClassType). But what is the type of () -> Unit? I have tried Function0 and it doesn't work. Can anyone help?
Class TestTarget:
Class TestTarget(private val mockTarget: MockTarget) {
fun testFunction() {
// some logic to be tested.
// call mockTarget.doSomething.
}
}
Class MockTarget
Class MockTarget {
fun doSomething(callback: () -> Unit) {
// some logic here.
}
fun doSomething(listener: OtherType) {
// Test code works without this function.
}
}
Test Code:
// setup mocks.
#Test
fun `verify testFunction`() {
`when`(mockTarget.doSomething(any())).thenAnswer { invocation ->
// callback here.
}
}
Replace org.mockito.Mockito with org.mockito.kotlin.*
and you can code like this
val anyLambda = any<() -> Unit>()
val anyListener = any<OtherType>()
In Kotlin (and Java 8) we can use Lambda expression to remove boilerplate callback interface. For example,
data class Profile(val name: String)
interface ProfileCallback {
fun onSuccess(profile: Profile)
}
class ProfileRepository(val callback: ProfileCallback) {
fun getProfile() {
// do calculation
callback.onSuccess(Profile("name"))
}
}
We can change remove ProfileCallback and change it into Kotlin's Lambda:
class ProfileRepository(val callback: (Profile) -> Unit) {
fun getProfile() {
// do calculation
callback(Profile("name"))
}
}
This works fine, but I'm not sure how to mock and then verify that function. I have
tried using Mockito like this
#Mock
lateinit var profileCallback: (Profile) -> Unit
#Test
fun test() {
// this wouldn't work
Mockito.verify(profileCallback).invoke(any())
}
but it throw an Exception:
org.mockito.exceptions.base.MockitoException: ClassCastException
occurred while creating the mockito mock : class to mock :
'kotlin.jvm.functions.Function1', loaded by classloader :
'sun.misc.Launcher$AppClassLoader#7852e922'
How to mock and verify Lambda expression in Kotlin? Is it even possible?
Here is example how you can achieve that using mockito-kotlin:
Given repository class
class ProfileRepository(val callback: (Int) -> Unit) {
fun getProfile() {
// do calculation
callback(1)
}
}
Using mockito-kotlin lib - you can write test mocking lambdas like this:
#Test
fun test() {
val callbackMock: (Int) -> Unit = mock()
val profileRepository = ProfileRepository(callbackMock)
profileRepository.getProfile()
argumentCaptor<Int>().apply {
verify(callbackMock, times(1)).invoke(capture())
assertEquals(1, firstValue)
}
}
I am trying to test my AkkaHTTP routes (written in Kotlin) using akka-http-testkit. The tests in our project use Spek and I would like to keep it this way.
The Route TestKit tutorial gives a Java example:
public class TestkitExampleTest extends JUnitRouteTest {
TestRoute appRoute = testRoute(new MyAppService().createRoute())
#Test
public void testCalculatorAdd() {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
// test responses to potential errors
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2"))
.assertStatusCode(StatusCodes.NOT_FOUND) // 404
.assertEntity("Request is missing required query parameter 'y'")
// test responses to potential errors
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2&y=three"))
.assertStatusCode(StatusCodes.BAD_REQUEST)
.assertEntity("The query parameter 'y' was malformed:\n" +
"'three' is not a valid 64-bit floating point value")
}
}
The setup uses the testRoute function, which means the test class must extend JUnitRouteTest.
Attempting to translate to a Kotlin Spek test I got this:
class TestKitExampleTest : JUnitRouteTest(), Spek({
describe("My routes") {
val appRoute = testRoute(MyAppService().createRoute())
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
//...rest omitted
}
}
})
which does not compile as the class is attempting to inherit two classes. I converted it to the following instead:
class TestKitExampleTest : Spek({
describe("My routes") {
val appRoute = testRoute(MyAppService().createRoute())
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
//...rest omitted
}
}
}) {
companion object : JUnitRouteTest()
}
which encouters the runtime error java.lang.IllegalStateException: Unknown factory null
at akka.http.impl.util.package$.actorSystem(package.scala:34).
Is there a way to use Akka's route testkit with Spek? Or is there another way to test these routes?
As #raniejade mentioned above, answered on Github. JUnitRouteTest bootstraps Akka with a rule, but Spek's LifeCycleListener can do the same thing.
Adding the code:
class SpekRouteBootstrapper: LifecycleListener, JUnitRouteTest() {
override fun beforeExecuteTest(test: TestScope) {
systemResource().before()
}
override fun afterExecuteTest(test: TestScope) {
systemResource().after()
}
}
allowed me to do this on the test class:
class TestKitExampleTest: Spek({
val bootstrapper = SpekRouteBootstrapper()
registerListener(bootstrapper)
describe("My routes") {
val appRoute by memoized {
bootstrapper.testRoute(MyAppService().createRoute())
}
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
}
}
})
I'm using Spek as test framework, and have troubles when sharing some test-steps for base classes.
I have an abstract base class and two derived classes.
abstract class Base {
abstract fun send()
}
class Foo : Base() {
override fun send() {}
fun anotherFunction() { }
}
class Bar : Base() {
override fun send() {}
fun differentFunction() { }
}
Now my question is: How can I create Speks for those classed, but only define the test for send() once in a base spek?
My first approach was to use SubjectSpek
class BaseSpek : SubjectSpek<Base>({
subject {
// ??? Can't instantiate Base because it is abstract
}
it("test the base") { ... }
})
class FooSpek : SubjectSpek<Foo>({
itBehavesLike(BaseSpek)
it("test anotherFunction") { ... }
})
My second approach was to use inheritance:
abstract class BaseSpek(base: Base) : Spek({
it("test the base") { ... }
})
abstract class FooSpek() : BaseSpek(???)
It seems that none of my approaches works. Any suggestions how to solve this? Should I bring this to the attention of the Spek-Author for possible changes in future releases of Spek?
SubjectSpek is the correct approach.
abstract class BaseSpec: SubjectSpek<Base>({
it("test base") { ... }
})
object FooSpec: BaseSpec<Foo>({
subject { ... }
// ugly for now, until Spek supports #Ignore
itBehavesLike(object: BaseSpec() {})
it("test another") { ... }
})