【问题标题】:Mockito - how to mock a variable instantiated in a method?Mockito - 如何模拟方法中实例化的变量?
【发布时间】:2021-01-19 14:22:29
【问题描述】:

我有以下Java 方法:

public Appointment addAppointment(String client, Appointment appointment) {

        String esbUrl = new ESBUrlHelper().getEsbUrl();
        AppointmentClient appointmentClient = AppointmentClientFactory.getUnsecuredClient(esbUrl);
        
        if (appointment.getId() == null) {
            outputAppointment = appointmentClient.addAppointment(client, appointment);
        } 

        return outputAppointment;
    }

上述方法调用第三方REST客户端appointmentClient

我遇到的问题是这导致我的测试失败。

如何在单元测试中模拟 appointmentClientobject?

目前我的测试如下:

@Test
public void shouldAddAppointment() {

    // act
    Appointment appointment = appointmentService.addAppointment(CLIENT_STRING, appointmentMock)

    // assert
    assertNotNull(appointment);
}

但我在appointmentClient.addAppointment(client, appointment); 行收到以下错误:

org.jboss.resteasy.client.exception.ResteasyIOException: IOException
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)

我想模拟如下:

 Mockito.when(appointmentClient.addAppointment(client, appointment)).thenReturn(appointmentMock);

【问题讨论】:

    标签: java rest junit mocking mockito


    【解决方案1】:

    您可以尝试为此使用 PowerMockito。 首先,您需要模拟AppointmentClientFactory 类的静态方法调用,如下所示:

    PowerMockito.mockStatic(AppointmentClientFactory.class); 
    PowerMockito.when(AppointmentClientFactory,"getUnsecuredClient",esbUrl).thenReturn(appointmentClient);
    

    此外,当您使用 PowerMockito 模拟静态方法时,请将 @PrepareForTest({AppointmentClientFactory.class}) 注释添加到测试类。

    【讨论】:

    • 请阅读stackoverflow.com/questions/9367610/…。以这种方式使用静态方法,在这种情况下绝对是反模式。是的,您可以通过这种方式解决当前问题,但最终会得到丑陋且难以维护的代码。
    【解决方案2】:

    使用您当前的代码,模拟对AppointmentClientFactory#getUnsecuredClient 的调用的唯一方法是使用PowerMock,因为工厂方法是静态的。这是由于您的调用代码addAppointment 和此处的依赖项(即AppointmentClientFactory)之间的硬耦合。

    如果我是你,我会避免这种情况,因为 PowerMock 不是进行测试的最佳方式。相反,我要做的是将AppointmentClientFactory 作为依赖项注入,从而允许我在测试期间模拟它的一个实例。

    这应该是双重方式的最佳方法。首先,因为您实现了不太紧密耦合的代码,其次因为您不需要使用PowerMock 进行单元测试。

    【讨论】:

    • 我想模拟的不是那个调用,我想模拟的是 AppointmentClient.addAppointment - 即客户端进行的 REST 调用
    • 您能否展示一个使用 Power mockito 的示例?谢谢
    • 是的,确实是这个,但是为了模拟这个调用,你需要一个模拟的 REST 客户端实例(或间谍)。使用您当前的实现,您将始终将addAppointment 调用到一个注定会失败的真实 REST 客户端实例。这就是您需要模拟工厂方法以提供模拟对象的原因。
    • 你能举一个在这种情况下使用 Powermock 的例子吗?
    • 不应该使用PowerMock,尤其是因为您可以控制正在测试的代码。我建议重构您的代码以遵循 DI 模式,以允许注入模拟依赖项。
    【解决方案3】:

    使用 Mockito 并非不可能。但是您最初的问题是AppointmentClientFactorystatic 方法。您绝对应该将此方法更改为实例方法(至少为了更好的架构),例如:

     public class AppointmentClientFactory {
    
        public AppointmentClient getUnsecuredClient(String url) {
            return new AppointmentClient(); //your implementation
        }
     }
    

    那么您的 AppointmentService 将看起来像(或接近它):

    public class AppointmentService {
    
        private final AppointmentClientFactory factory;
    
        public AppointmentService() {
            this(new AppointmentClientFactory());
        }
    
        public AppointmentService(AppointmentClientFactory factory) {
            this.factory = factory;
        }
    
        public Appointment addAppointment(String client, Appointment appointment) {
            String esbUrl = "";
            Appointment outputAppointment = null;
            AppointmentClient appointmentClient = new AppointmentClientFactory().getUnsecuredClient(esbUrl);
            if (appointment.getId() == null) {
                outputAppointment = appointmentClient.addAppointment(client, appointment);
            }
            return outputAppointment;
        }
    }
    

    然后你可以编写如下测试:

    public class AppointmentTest {
    
        private final String CLIENT_STRING = "";
    
        @Test
        public void shouldAddAppointment() {
            AppointmentClientFactory clientFactory = Mockito.mock(AppointmentClientFactory.class);
            AppointmentClient mockedClient = Mockito.mock(AppointmentClient.class);
            AppointmentService service = new AppointmentService(clientFactory);
            Appointment appointmentMock = new Appointment();
            when(clientFactory.getUnsecuredClient(any())).thenReturn(mockedClient);
    
            Appointment appointment = service.addAppointment(CLIENT_STRING, appointmentMock);
    
            assertNotNull(appointment);
        }
    
    }
    

    【讨论】:

    • 感谢您的详细回复,我忘了补充一点,我的 AppointmentService 类使用 EJB 作为依赖注入。
    • 您可以通过构造函数将您的 AppointmentClientFactory 注入到 ApppointmenService 中。然后你可以在你的测试中使用构造函数,就像我上面写的那样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多