如何使用 Mockito/Powermock 模拟枚举单例类?

Posted

技术标签:

【中文标题】如何使用 Mockito/Powermock 模拟枚举单例类?【英文标题】:How to mock an enum singleton class using Mockito/Powermock? 【发布时间】:2013-04-03 01:43:45 【问题描述】:

我不确定如何模拟枚举单例类。

public enum SingletonObject
  INSTANCE;
  private int num;

  protected setNum(int num) 
    this.num = num;
  

  public int getNum() 
    return num;
  

我想在上面的例子中存根 getNum(),但我不知道如何模拟实际的 SingletonObject 类。我认为使用 Powermock 来准备测试会有所帮助,因为枚举本质上是最终的。

//... rest of test code
@Test
public void test() 
  PowerMockito.mock(SingletonObject.class);
  when(SingletonObject.INSTANCE.getNum()).thenReturn(1); //does not work

这是使用 PowerMockMockito 1.4.10 和 Mockito 1.8.5。

【问题讨论】:

检查类似的线程:***.com/questions/2302179/mocking-a-singleton-class 感谢 Martin,我通读了该线程,它看起来像是一种使用非枚举方式实现单例的方法,并且我能够使用该方法正确使用模拟。但是,有没有办法模拟一个枚举单例类?从我收集到的信息来看,枚举单例类是在 java 1.5 之后声明单例的推荐方法。 Re: PowerMock : can i mock enums? 单身人士是邪恶的 :) 【参考方案1】:

如果你想删除 INSTANCE 返回的内容,你可以这样做,但它有点讨厌(使用反射和字节码操作)。我使用 PowerMock 1.4.12 / Mockito 1.9.0 创建并测试了一个包含三个类的简单项目。所有类都在同一个包中。

SingletonObject.java

public enum SingletonObject 
    INSTANCE;
    private int num;

    protected void setNum(int num) 
        this.num = num;
    

    public int getNum() 
        return num;
    

SingletonConsumer.java

public class SingletonConsumer 
    public String consumeSingletonObject() 
        return String.valueOf(SingletonObject.INSTANCE.getNum());
    

SingletonConsumerTest.java

import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SingletonObject.class)
public class SingletonConsumerTest 
    @Test
    public void testConsumeSingletonObject() throws Exception 
        SingletonObject mockInstance = mock(SingletonObject.class);
        Whitebox.setInternalState(SingletonObject.class, "INSTANCE", mockInstance);

        when(mockInstance.getNum()).thenReturn(42);

        assertEquals("42", new SingletonConsumer().consumeSingletonObject());
    

Whitebox.setInternalState 的调用将INSTANCE 替换为您可以在测试中操作的模拟对象。

【讨论】:

但是你怎么能模拟 SingletonObject.class 呢?它会给 MockitoException 注释“Mockito 无法模拟/监视以下内容:-最终类-匿名类-原始类型” 这使用了 PowerMock。 嗨,我在使用Whitebox.setInternalState 时收到了java.lang.IllegalAccessException: Can not set static final。你知道为什么吗? 你在你的测试类中使用了@PrepareForTest注解吗? 我尝试了同样的方法,但我的枚举的构造函数仍在执行,这是我不想要的。上述解决方案是否也模拟了枚举的构造函数?【参考方案2】:

与您打算模拟的方法有一个接口

public interface SingletonInterface 
  int getNum();

让枚举实现接口

public enum SingletonObject implements SingletonInterface 
    INSTANCE;
    private int num;

    protected void setNum(int num) 
        this.num = num;
    

    @Override
    public int getNum() 
        return num;
    

模拟界面

@Test
public void test() 
  SingletonInterface singleton = Mockito.mock(SingletonInterface.class);
  when(singleton.getNum()).thenReturn(1); //does work

【讨论】:

这个答案是最好的【参考方案3】:

除了上面的 Matt Lachman 回答之外,在SingleTonConsumerTest.class 中创建一个用于 power mock 的对象工厂

@ObjectFactory
public IObjectFactory getObjectFactory() 
    return new org.powermock.modules.testng.PowerMockObjectFactory();

这将删除Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types 错误。

【讨论】:

【参考方案4】:

我不确定这是否是最好的方法,但就我而言,到目前为止它运行良好。我正在使用 mockito-core 和 mockito-inline (3.11.0),我在间谍的帮助下嘲笑我的单身人士。您的班级示例:

SingletonObject so = SingletonObject.INSTANCE;
private SingletonObject spySo = Mockito.spy(so);

@Before
public void setUp() 
    Mockito.doReturn(10).when(spySo).getNum();


@Test
public void simpleTest() 
    assertThat(spySo.getNum(), is(10));

【讨论】:

【参考方案5】:

Mockito 文档在 enum 用例上没有明确说明:

Mocking final types, enums and final methods (Since 2.1.0)

Powermock 支持:

兼容 JUnit 4

build.gradle** 也应该与 Maven 一起使用)

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    testImplementation 'org.mockito:mockito-inline:4.0.0'
    testImplementation 'org.mockito:mockito-junit-jupiter:4.1.0'

关于可用的实验功能(Powermock + JUnit 5),可以尝试一下:

    implementation group: 'org.powermock', name: 'powermock-module-junit5', version: '1.6.4'
    testImplementation 'org.powermock:powermock-mockito-release-full:1.6.4'

枚举:

public enum ConnectionFactory 
   INSTANCE;

   public Connection get() throws SQLException 
      //
      // Load url from configuration file  ('application.yml')
      //
      final var connection = DriverManager.getConnection(url);
      connection.setAutoCommit(false);

      return connection;
   

可以使用这些方法:

class DatabaseUnitTests 

   private final ConnectionFactory connectionFactory = Mockito.spy(ConnectionFactory.INSTANCE);


   //Or

   @InjectMocks
   private final ConnectionFactory connectionFactory = Mockito.mock(ConnectionFactory.class);


   @Test
   void check_is_database_connection_is_OK()
      Mockito.doReturn(mockedConnection()).when(connectionFactory).get();
       
      // Do something
   

【讨论】:

以上是关于如何使用 Mockito/Powermock 模拟枚举单例类?的主要内容,如果未能解决你的问题,请参考以下文章

模拟系统类时的 Mockito + PowerMock LinkageError

text Mockito,PowerMock依赖项

单元测试及框架简介 --junitjmockmockitopowermock的简单使用

如何使用@WebMvcTest 春季测试在模拟服务中注入模拟的restTemplate

如何判断是不是被检测到使用模拟器了

使用模拟商店时如何在ngxs中模拟@Select