【问题标题】:How to mock a method in an ENUM class?如何模拟 ENUM 类中的方法?
【发布时间】:2019-04-30 18:38:12
【问题描述】:

我正在为下面的 ENum 类编写 JUNIT 测试用例。我下面的课程只会给我运行我的代码的当前机器的主机名。在我编写 JUNIT 测试时,如何模拟下面的类,以便我可以随时更改 getHostName() 方法,以便每当我调用 getDatacenter() 时,它可以通过模拟返回我传递的任何主机名.我不想让它参数化。

我只是想在模拟主机名的同时测试某些情况。

public enum DatacenterEnum {
    DEV, DC1, DC2, DC3;


    public static String forCode(int code) {
    return (code >= 0 && code < values().length) ? values()[code].name() : null;
    }
    private static final String getHostName() {
        try {
            return InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();
        } catch (UnknownHostException e) {
            s_logger.logError("error = ", e);
        }

        return null;
    }

    public static String getDatacenter() {
        return getHostName();
    }
}

【问题讨论】:

  • 这些方法必须在枚举类中吗?枚举值和这些方法之间似乎没有任何引用。
  • 我已经从代码中删除了这些信息。有几种方法可以相应地从主机名中使用这些详细信息。所以我的主要吸引力中心是嘲笑 getHostName 方法,其他代码围绕它旋转..

标签: java junit enums jmockit


【解决方案1】:

有可能,但不推荐,最好重构代码。

使用 Mockito/PowerMock 的工作示例

@RunWith(PowerMockRunner.class)
@PrepareForTest(DatacenterEnum.class)
public class DatacenterEnumTest {

    @Mock
    InetAddress inetAddress;

    @Test
    public void shouldReturnDatacenter() throws UnknownHostException {
        //given
        mockStatic(InetAddress.class);
        given(inetAddress.getCanonicalHostName()).willReturn("foo");
        given(InetAddress.getLocalHost()).willReturn(inetAddress);

        //when
        String datacenter = DatacenterEnum.getDatacenter();

        //then
        assertThat(datacenter).isEqualTo("foo");
    }
}

依赖关系

  • org.powermock:powermock-module-junit4:1.5.2
  • org.powermock:powermock-api-mockito:1.5.2
  • org.assertj:assertj-core:1.5.0
  • junit:junit:4.11

【讨论】:

  • 导入是什么样的?
  • 为什么不推荐?这应该如何重构?
【解决方案2】:

使用 JMockit 很容易:

@Test
public void mockInetAddress(@Cascading final InetAddress inetAddress)
{
    new NonStrictExpectations() {{
        inetAddress.getCanonicalHostName(); result = "foo";
    }};

    String datacenter = DatacenterEnum.getDatacenter();

    assertEquals("foo", datacenter);
}

当然,您也可以在枚举中模拟 getHostName() 方法,但最好避免模拟 private 方法。

【讨论】:

    【解决方案3】:

    您可以创建一个数据中心接口并让枚举实现该接口。这将使模拟更容易。

    首先,我不会将配置信息放在枚举中。如果您必须添加其他数据中心(或数据中心的配置更改),则必须重新编译代码。考虑将配置放入普通类读取中,例如 java 属性文件或 XML 文件。 (这个函数可能已经在你的框架中实现了。)

    如果这不可能,您可以使用“darkes reflation”魔法将 Enum 中的字段更改为所需的值。

    【讨论】:

      【解决方案4】:

      这是您可以使用 Mockito/Powermock 实现的一种方式。您需要 Powermock,因为 Mockito 无法模拟静态方法:

      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.powermock.core.classloader.annotations.PrepareForTest;
      import org.powermock.modules.junit4.PowerMockRunner;
      import static org.junit.Assert.assertEquals;
      import static org.powermock.api.mockito.PowerMockito.mockStatic;
      import static org.powermock.api.mockito.PowerMockito.when;
      
      @RunWith(PowerMockRunner.class)
      @PrepareForTest({DatacenterEnum.class})
      public class DatacenterEnumTest {
      
          @Test
          public void testGetDatacenter() {
              mockStatic(DatacenterEnum.class);
              when(DatacenterEnum.getDatacenter()).thenReturn("YourHostname");
      
              String datacenter = DatacenterEnum.getDatacenter();
      
              assertEquals("YourHostname", datacenter);
          }
      }
      

      Maven 依赖项

      <dependencies>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.11</version>
          </dependency>
          <dependency>
              <groupId>org.powermock</groupId>
              <artifactId>powermock-api-mockito</artifactId>
              <version>1.5.2</version>
          </dependency>
          <dependency>
              <groupId>org.powermock</groupId>
              <artifactId>powermock-module-junit4</artifactId>
              <version>1.5.2</version>
          </dependency>
      </dependencies>
      

      【讨论】:

        【解决方案5】:

        我可能是老派,但我真的会重构被测代码,而不是使用类加载器黑客。比如:

        public enum DatacenterEnum {
            DEV, DC1, DC2, DC3;
        
        
            static String hostName = InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();
        
            public static String getHostName() {
                return hostName;
            }
        }
        

        在您的测试代码中,在运行测试之前:

        DataCenterEnum.hostName = "foo";
        

        【讨论】:

        • 这只会部分起作用。 ClassLoader 的规则是在它的第一个引用上加载一个类,这需要静态初始化器。 OP 抛出了 UnknownHostException,现在您已将其移至静态字段初始化,该初始化现在有机会将异常传播到 ClassLoader。如果要抛出某种已检查或未检查的异常,您未来对hostName 的分配将导致NoClassDefFoundError。将引发异常的方法放在未经检查的初始化程序中并不是一个好主意。
        • 也许我过于热心地删除了那个 catch 块,但我无法想象 DNS 不知道本地主机名的情况?但是,是的,如果你真的看到这个异常被抛出,我会将查找移到一个专用的静态方法中并添加一个 catch 块,这样类就可以正确初始化,并且测试代码有机会覆盖主机名。
        • 好吧,是 InetAddress#getLocalHost() 函数本身引发了异常,而 javadocs 似乎只表明 if the local host name could not be resolved into an address. 但公平地说,我自己并没有真正考虑过这一点来思考为什么会这样发生。但它确实发生在以下情况下:stackoverflow.com/questions/1881546/…。更重要的是,我只是为了减少维护而采取防御性编程的心态。
        猜你喜欢
        • 1970-01-01
        • 2020-01-03
        • 2018-05-04
        • 2015-08-26
        • 1970-01-01
        • 1970-01-01
        • 2021-12-31
        • 2022-07-27
        • 2022-07-06
        相关资源
        最近更新 更多