Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spock with Mockito testing Kotlin classes

I have some tests written in Spock which covers my Java code. Now I migrate to Kotlin and the problem is i cannot mock final classes so i decide to use Mockito plugin described here: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable

The question is do I need to replace every '_' '>>' with Mockitos' any(), anyString(), when(), then() etc? I tried to just mock final classes using Mockito but it seems not work.

If i have to replace what the advantage is for using Spock testing framework in this case? Maybe i should remove it and stay with Mockito only?

like image 777
minizibi Avatar asked Jan 03 '23 22:01

minizibi


1 Answers

I do not use Kotlin, but I have used PowerMock before for mocking final classes/methods and static methods. I was wondering about whether it might be possible to use Spock's GroovyMock and global GroovySpy features and tested it against Java code using Spock 1.1-groovy-2.4. In a first quick & dirty test scenario it seems to work:

Java class under test:

package de.scrum_master.stackoverflow;

public final class FinalClass {
  public static final String finalStaticMethod() {
    return "x";
  }

  public final String finalMethod() {
    return "x";
  }
}

Spock test:

package de.scrum_master.stackoverflow

import spock.lang.Specification

/**
 * See https://stackoverflow.com/q/48391716/1082681
 * See http://spockframework.org/spock/docs/1.1/all_in_one.html#GroovyMocks
 */
class FinalClassTest extends Specification {
  def "use GroovyMock for final method in final class"() {
    given:
    FinalClass finalClass = GroovyMock() {
      finalMethod() >> "mocked"
    }

    expect:
    finalClass.finalMethod() == "mocked"
  }

  def "use global GroovySpy for final static method in final class"() {
    given:
    GroovySpy(FinalClass, global: true)
    FinalClass.finalStaticMethod() >> "mocked"

    expect:
    FinalClass.finalStaticMethod() == "mocked"
  }
}

For me both feature methods were green when running the test. Maybe you want to give it a try with my example and subsequently with your Kotlin classes - no guarantees for the latter from me, though.


Attention: The Spock manual says:

When called from Java code, Groovy mocks will behave like regular mocks.

So maybe you will get disappointed when injecting Groovy Mocks as dependencies into Java classes under test.


Update: Okay, I tested it with another Java class using those fancy GroovyMocks and - as mentioned above - it does not work:

Java class using the mocked class as a dependency:

package de.scrum_master.stackoverflow;

public class AnotherClass {
  public String doSomething(FinalClass finalClass) {
    return finalClass.finalMethod();
  }

  public String doSomethingElse() {
    return FinalClass.finalStaticMethod();
  }
}

Spock test:

package de.scrum_master.stackoverflow

import spock.lang.Specification

/**
 * See https://stackoverflow.com/q/48391716/1082681
 * See http://spockframework.org/spock/docs/1.1/all_in_one.html#GroovyMocks
 */
class AnotherClassTest extends Specification {
  def "indirectly use GroovyMock for final method in final class"() {
    given:
    FinalClass finalClass = GroovyMock() {
      finalMethod() >> "mocked"
    }

    expect:
    new AnotherClass().doSomething(finalClass) == "mocked"
  }

  def "indirectly use global GroovySpy for final static method in final class"() {
    given:
    GroovySpy(FinalClass, global: true)
    FinalClass.finalStaticMethod() >> "mocked"

    expect:
    new AnotherClass().doSomethingElse() == "mocked"
  }
}

Unfortunately, both tests fail because the methods do not get stubbed when used from the Java class. I.e. you are stuck with PowerMock or Mockito. But still you can use all the other nice Spock features such as data tables, @Unroll and many more.


Update 2: THE SOLUTION

Add this to your Maven build (if you use Gradle, do something similar):

<dependency>
  <groupId>de.jodamob.kotlin</groupId>
  <artifactId>kotlin-runner-spock</artifactId>
  <version>0.3.1</version>
  <scope>test</scope>
</dependency>

Now you can use the SpotlinTestRunner from project kotlin-testrunner in combination with annotations like

  • @OpenedClasses([Foo, Bar, Zot])
  • @OpenedPackages(["de.scrum_master.stackoverflow", "my.other.package"])

Of course this will not work for static methods (you still need PowerMock for it), but your question was about non-static methods in closed Kotlin classes. With this test runner you can just mock them because a special classloader opens them up via Javassist before test execution:

package de.scrum_master.stackoverflow

import de.jodamob.kotlin.testrunner.OpenedClasses
import de.jodamob.kotlin.testrunner.OpenedPackages
import de.jodamob.kotlin.testrunner.SpotlinTestRunner
import org.junit.runner.RunWith
import spock.lang.Specification

/**
 * See https://stackoverflow.com/q/48391716/1082681
 * See https://github.com/dpreussler/kotlin-testrunner
 */
@RunWith(SpotlinTestRunner)
@OpenedClasses(FinalClass)
//@OpenedPackages("de.scrum_master.stackoverflow")
class AnotherClassSpotlinRunnerTest extends Specification {
  def "use SpotlinRunner to stub final method in final class"() {
    given:
    FinalClass finalClass = Stub() {
      finalMethod() >> "mocked"
    }

    expect:
    new AnotherClass().doSomething(finalClass) == "mocked"
  }
}
like image 168
kriegaex Avatar answered Jan 05 '23 14:01

kriegaex