【问题标题】:Mock private static final field using mockito or Jmockit使用 mockito 或 Jmockit 模拟私有静态最终字段
【发布时间】:2015-08-22 13:34:28
【问题描述】:

我在我的班级中使用 private static final LOGGER 字段,我希望 LOGGER.isInfoEnabled() 方法返回 false。 如何使用 mockito 或 jMockit 模拟静态 final 字段

我的班级是:

  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  public class Class1 {

  private static final Logger LOGGER = LoggerFactory.getLogger(Class1.class);

    public boolean demoMethod() {
       System.out.println("Demo started");
       if (LOGGER.isInfoEnabled()) {
         System.out.println("@@@@@@@@@@@@@@ ------- info is enabled");
       } else {
         System.out.println("info is disabled");
       }
       return LOGGER.isInfoEnabled();
    }
  }

它的junit是:

import mockit.Mocked;
import mockit.NonStrictExpectations;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.testng.Assert.*;
import com.source.Class1;

public class MyTest {

  @InjectMocks
  Class1 cls1;

  @BeforeMethod
  public void initMocks() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void test(@Mocked final Logger LOGGER) {

    new NonStrictExpectations() {
      {
        LOGGER.isInfoEnabled();
        result = false;
      }
    };
    assertFalse(cls1.demoMethod());
  }
}

当我运行它时,结果是:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.test.MyTest
Configuring TestNG with: TestNG652Configurator
Demo started
@@@@@@@@@@@@@@ ------- info is enabled
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.9 sec <<< FAILURE! - in com.test.MyTest
test(com.test.MyTest)  Time elapsed: 0.168 sec  <<< FAILURE!
java.lang.AssertionError: expected [false] but found [true]
        at com.test.MyTest.test(MyTest.java:35)


Results :

Failed tests:
  MyTest.test:35 expected [false] but found [true]

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.899s
[INFO] Finished at: Mon Jun 08 12:35:36 IST 2015
[INFO] Final Memory: 16M/166M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test (default-test) on project JMockDemo: The
re are test failures.
[ERROR]
[ERROR] Please refer to D:\perfoce_code\workspace_kepler\JMockDemo\target\surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

我是 jmockit 的新手,我希望我上面的 junit 案例能够成功运行。 而且我必须使用 JMockit 或 mockito,不能使用 Powermockito。 请帮忙。

【问题讨论】:

  • 您可以为这些元素创建一个setter方法并在测试时设置一个模拟记录器。
  • 是的,它是这样工作的,但是我必须使记录器不是最终的,并且还要添加一个 setter 方法。有没有办法在不改变实际课程的情况下做到这一点。
  • 只需将@Mocked Logger 更改为@Capturing Logger,它应该可以工作。
  • 感谢@Rogério,它也在工作,现在我有了另一个工作解决方案:-)
  • 请参阅stackoverflow.com/a/60988775/812093 了解如何使用普通的 Mockito 进行操作

标签: java junit mockito static-members jmockit


【解决方案1】:

一种方法是使用反射从字段中删除 final 修饰符,然后将 LOGGER 字段替换为 Mocked one

public class Class1Test {
    @Test
    public void test() throws Exception {
        Logger logger = Mockito.mock(Logger.class);
        Mockito.when(logger.isInfoEnabled()).thenReturn(false);
        setFinalStatic(Class1.class.getDeclaredField("LOGGER"), logger);
        Class1 cls1 = new Class1();
        assertFalse(cls1.demoMethod());
    }

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);        
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
}

【讨论】:

  • 哇太好了,这就是我正在寻找的解决方案,非常感谢:-)
  • 为什么把 setFinalStatic 方法设为静态,有什么具体原因吗?
  • 这是非常危险的,因为它会改变整个 ClassLoader 的静态实例。任何后续测试/类都将具有该记录器的模拟实例。在并行运行测试时也不应该使用它。这可能会在您的测试中导致严重的多线程问题,请注意这一点。
  • 愚蠢的模拟框架,如果我想模拟它,那么它应该自动为我更改修饰符。
  • @IlkerCat 我快速搜索并没有在我的一个项目中找到示例(已经很长时间了)。如果我没记错的话应该可以:stackoverflow.com/questions/5385161/…:D
【解决方案2】:

公认的解决方案不应该与 JDK 12 一起使用。原因可见here

使用 PowerMockito(已在 2.0.9 版测试)很容易做到这一点。您可以使用 Whitebox.setInternalState 方法为您完成。

例子:

Whitebox.setInternalState(MyTestClass.class, "myCar", carMock);

MyTestClass 是包含该字段的类。

myCar 是字段的变量名。

carMock 是你想要传递的一些模拟。

【讨论】:

  • 这对我有用,而且很简单。我不必从记录器中删除最终修改器
  • PowerMockito Whitebox 2.0.9 在私有静态 final 上引发异常:原因:java.lang.IllegalAccessException:无法设置静态最终 bla-bla 字段 bla-bla。所以反射只是解决方案恕我直言
  • 如果字段只是 final 而不是静态的,这将不起作用。
【解决方案3】:

我认为 mockito 或 jMockit 不能模拟静态最终类,因为它们在单元测试时试图覆盖方法。但是,powerMockito 可以使用反射并模拟静态最终类/方法。

【讨论】:

  • 你不能覆盖 final 方法。只有使用反射才能做到这一点。
【解决方案4】:

在参数中将“@Mocked Logger”更改为“@Capturing Logger”使其工作。喜欢

  @Test
  public void test(@Capturing final Logger LOGGER) {

    new NonStrictExpectations() {
      {
        LOGGER.isInfoEnabled();
        result = false;
      }
    };
    assertFalse(cls1.demoMethod());
  }

【讨论】:

    猜你喜欢
    • 2013-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多