【问题标题】:Mock JNI methods in Java在 Java 中模拟 JNI 方法
【发布时间】:2021-08-27 07:47:16
【问题描述】:

我正在使用 PowerMockito 和 JUnit。如何模拟 JNI 函数?

Class A{
  public native String test();
}
Class Util{
  public static String test() {
    A a = new A();
    return a.test();
  }
}

单元测试:

Class ATest{
  @Test
  public void test1() {
    A a = PowerMockito.mock(A.class);
    PowerMockito.when(a.test()).thenReturn("test");
    boolean check = Util.test().equals("test")
    assert check == true
  }
}

如果我运行测试,我得到的测试是原生的。如何解决这个问题?

【问题讨论】:

  • 你确定,你使用的是 Spock,代码看起来更像是带有 groovy 的 JUnit。
  • 在 Spock 中,您可以像任何其他方法一样模拟本机方法。不需要 Mockito 或 PowerMock,您只需使用 Spock 模拟即可。您唯一需要使用 Mockito、PowerMock 或 Sarek 等附加模拟工具的就是模拟实用程序类的静态方法。不过,如果我是你,我会避免这种情况。只需重构实用程序类即可通过方法参数或带有 setter 方法的静态成员注入 A 实例。然后你可以简单地注入一个普通的 mock 并且不用额外的花哨的工具来掩盖你糟糕的应用程序设计。去重构吧!
  • 此外,您以错误的方式使用 PowerMock:您正在创建一个模拟,但没有将其注入静态方法,与上述相同的问题。您还必须存根构造函数调用,确保构造函数返回您的模拟。所有这些都是错误的设计决策的结果,即在方法中创建依赖实例作为局部变量,使代码紧密而不是松散耦合,这也阻碍了可测试性。如果你坚持的话,你可以使用 PowerMock 来解决这个问题,但是不得不使用那个工具已经很糟糕了。
  • 昨天忘记在这里发布我的发现了。 @kriegaex 是对的。我错过了嘲笑构造函数。这已经解决了这个问题。我使用了 mock,因为我必须验证方法调用。
  • 那么请写一个全面的答案,展示解决您问题的代码,为大家谋福利。您向社区寻求帮助,现在您有机会向同一个社区返回一些东西。还请考虑重构您(几乎)不可测试的静态辅助方法。相信我,在您告别 PowerMock 之后,您作为开发人员的生活会好很多。

标签: unit-testing java-native-interface powermock powermockito


【解决方案1】:

我建议摆脱 PowerMock 并按如下方式重构您的代码。我使用了我在某处找到的示例项目中的本机方法,不完全是您的示例,但您明白了:

package com.baeldung.jni;

public class HelloWorldJNI {
  static {
    System.loadLibrary("native");
  }

  public static void main(String[] args) {
    new HelloWorldJNI().sayHello();
  }

  public native String sayHello();
}
#include "com_baeldung_jni_HelloWorldJNI.h"
#include <iostream>

/*
 * Class:     com_baeldung_jni_HelloWorldJNI
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello (JNIEnv* env, jobject thisObject) {
    std::string hello = "Hello from C++ !!";
    std::cout << hello << std::endl;
    return env->NewStringUTF(hello.c_str());
}
package de.scrum_master.stackoverflow.q68949748;

import com.baeldung.jni.HelloWorldJNI;

public class Util {
  private static HelloWorldJNI helloWorld = new HelloWorldJNI();

  public static void setHelloWorld(HelloWorldJNI helloWorld) {
    Util.helloWorld = helloWorld;
  }

  public static void doSomethingUseful() {
    System.out.println(helloWorld.sayHello());
  }
}
package de.scrum_master.stackoverflow.q68949748

import com.baeldung.jni.HelloWorldJNI
import spock.lang.Specification

class BaledungJNITest extends Specification {
  def "verify original native method"() {
    given:
    HelloWorldJNI helloWorld = new HelloWorldJNI()

    expect:
    helloWorld.sayHello() == "Hello from C++ !!"
  }

  def "mock native method"() {
    given:
    HelloWorldJNI helloWorld = Mock() {
      sayHello() >> "mocked"
    }

    expect:
    helloWorld.sayHello() == "mocked"
  }

  def "inject mock into utility class"() {
    given:
    HelloWorldJNI helloWorld = Mock()
    Util.setHelloWorld(helloWorld)

    when:
    Util.doSomethingUseful()

    then:
    1 * helloWorld.sayHello() >> "mocked"
  }

}

控制台日志是:

Hello from C++ !!
mocked

第一行日志来自特征方法verify original native method,第二行来自inject mock into utility class。另一种特征方法不产生任何日志输出。

看到了吗?没有 Mockito 或 PowerMock,只是普通的 Spock 模拟,经过小的重构,没有任何花哨的东西,使依赖项可注入。

【讨论】:

    【解决方案2】:
    @Test
    public void test1() {
      A a = PowerMockito.mock(A.class);
      PowerMockito.when(a.test()).thenReturn("test");
      PowerMockito.whenNew(A.class).withNoArguments().thenReturn(a);
    
      boolean check = Util.test().equals("test");
      assert check
    }
    

    这对我有用。我嘲笑了构造函数的调用。

    【讨论】:

    • 就像 Leonard 之前所说的,它是 JUnit 测试,而不是 Spock 测试。您的问题被标记为“spock”​​。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 2012-09-18
    • 1970-01-01
    相关资源
    最近更新 更多