Spock与Mockito测试Kotlin类

4
我有一些用Spock编写的测试代码覆盖了我的Java代码。现在我迁移到Kotlin,问题是我无法模拟final类,因此我决定使用Mockito插件,描述如下:https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable 问题是,我需要将每个'_'和'>>'替换为Mockito的any()、anyString()、when()、then()等吗?我尝试使用Mockito模拟final类,但似乎不起作用。
如果我必须进行替换,那么在这种情况下使用Spock测试框架的优势是什么?也许我应该删除它,只使用Mockito?

似乎Spock正在计划添加支持。 在此之前,我还没有看到任何使用Spock&Mockito的情况,其中被模拟的类不使用when等方法。 我遇到的唯一一个同时使用Spock&Mockito但未使用这些方法的示例是由Spock执行模拟操作的,而(如您发现的)它目前不支持模拟“final”类。 如果Spock无法满足您的要求,则最好降低依赖性并坚持使用Mockito。 - Dioxin
你有没有考虑开放类(也许只是为了测试而不那么好看)或提取接口(非常适合解耦组件和可测试性)?反正你正在迁移,所以你可以稍微改进一下你的应用程序设计。 - kriegaex
谢谢大家,我决定同时使用Mockito和Spock。 - minizibi
1个回答

9
我不使用 Kotlin,但我以前使用过 PowerMock 来模拟 final 类/方法和静态方法。我想知道是否可能使用 Spock 的 GroovyMock 和全局 GroovySpy 功能,并在使用 Spock 1.1-groovy-2.4 测试 Java 代码时进行测试。在第一个快速而粗略的测试场景中,它似乎可以工作:
Java 测试类:
package de.scrum_master.stackoverflow;

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

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

斯波克测试:

package de.scrum_master.stackoverflow

import spock.lang.Specification

/**
 * See https://dev59.com/OKjja4cB1Zd3GeqP-m63
 * 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"
  }
}

对我来说,测试运行时两种特性方法都是绿色的。也许你可以用我的例子试一试,然后再尝试你的Kotlin类 - 不过后者我不能保证。


注意:Spock手册中提到:

当从Java代码调用时,Groovy Mocks将表现得像普通的mocks。

因此,当将Groovy Mocks注入到正在测试的Java类的依赖项中时,可能会感到失望。


更新:好的,我用另一个使用那些花哨的GroovyMocks的Java类进行了测试,正如上面提到的,它不起作用:

使用模拟类作为依赖项的Java类:

package de.scrum_master.stackoverflow;

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

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

Spock测试:

package de.scrum_master.stackoverflow

import spock.lang.Specification

/**
 * See https://dev59.com/OKjja4cB1Zd3GeqP-m63
 * 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"
  }
}

不幸的是,这两个测试都失败了,因为当从Java类中使用时,这些方法并没有被存根。例如,您只能使用PowerMock或Mockito。但是,您仍然可以使用所有其他很棒的Spock功能,例如数据表、@Unroll等等。


更新2: 解决方案

将以下内容添加到您的Maven构建中(如果您使用Gradle,请执行类似操作):

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

现在你可以使用来自项目kotlin-testrunnerSpotlinTestRunner与注释结合使用,例如:

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

当然,这对于静态方法无效(你仍然需要PowerMock),但你的问题是针对封闭Kotlin类中的非静态方法。使用此测试运行程序,您只需模拟它们,因为一个特殊的类加载器会在测试执行之前通过Javassist打开它们:

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://dev59.com/OKjja4cB1Zd3GeqP-m63
 * 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"
  }
}

1
更新2:已经有一个解决方案了:SpotlinTestRunner。请查看我的编辑。 - kriegaex
这个贴子被踩的原因是什么?回答解释了很多,提供了示例代码,还更新了其他解决方案等。如果您可以留下您的评论,我将不胜感激。 - kriegaex
1
可能是因为该解决方案已经不再起作用了。Spock 2.0不再支持Sputnik。请参见:https://github.com/spockframework/spock/issues/735 - Faur Ioan-Aurel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接