【问题标题】:Refactoring: Mockito with new instance creation重构:创建新实例的 Mockito
【发布时间】:2018-11-14 12:49:29
【问题描述】:

我有一个类是其他应用程序的客户端。

class StarFleetClient {
    private RestTemplate restTemplate = null;
    private String accessToken = null;

    public StarFleetClient(String address, String username, String password) {
        restTemplate = new RestTemplate();
        accessToken = restTemplate.postForEntity(...);
    }

    public String attack() {
        return restTemplate.exchange(... )
    }
}

现在,我会在需要时创建此类的多个实例。每次凭据都会不同。

new StarFleetClient("address1", "user1", "pass");

其他时间

new StarFleetClient("address1", "user1", "pass");

等等……

现在,这是完美的工作,但是当我编写单元测试时会出现问题。我不想在运行测试时调用实际的星际舰队。我需要模拟RestTemplate,但在创建新实例时,我无法正确模拟。

如何为此编写测试用例?或者我怎样才能使这段代码可测试?

技术栈:Spring Boot、Mockito

【问题讨论】:

  • 为什么不尝试使用TestRestTemplate,它是RestTemplate 的方便替代品,适用于集成测试。它们是容错的,并且可以选择携带基本身份验证标头。

标签: unit-testing spring-boot mockito refactoring


【解决方案1】:

您可以使用@Mock@InjectMocks 注释来实现您想要的。为了简单起见,我在我的代码摘录中用restTemplate.toString() 替换了restTemplate.postForEntityrestTemplate.exchange 方法。

所以,鉴于StarFleetClient

class StarFleetClient {
    private RestTemplate restTemplate = null;
    private String accessToken = null;

    public StarFleetClient(String address, String username, String password) {
        restTemplate = new RestTemplate();
        accessToken = restTemplate.toString();
    }

    public String attack() {
        return restTemplate.toString();
    }
}

模拟 RestTemplate 的 Mockito 测试可能是:

@RunWith(MockitoJUnitRunner.class)
public class StarFleetClientTest {

    @Mock
    RestTemplate restTemplate;

    @InjectMocks
    StarFleetClient starFleetClient = new StarFleetClient("somewhere", "user", "password");

    @Test
    public void testStarFleetAttack() {
        Mockito.when(restTemplate.toString()).thenReturn("called Mocked RestTemplate");
        assertEquals("called Mocked RestTemplate", starFleetClient.attack());
    }
}

【讨论】:

  • 这可行,但我的用例有点不同。我在构造函数本身中使用restTemplate.postForEntity(...)。一旦构造函数完成,模拟将起作用,但在构造函数执行时模拟将不起作用。
  • 测试构造函数很困难,特别是当您不想在构造函数的参数中传递RestTemplate 时。我建议创建一个工厂来实例化StarFleetClient 或将accessToken = restTemplate.postForEntity(...); 行提取到一个新的公共方法让我们说public void initialiaze() 然后调用它
  • 我完全理解。我愿意重构代码,但我不知道如何重构这段代码。我已经创建了一个工厂,所以我可以测试StarFleetClient 的消费者。但是当我测试课程本身时,我遇到了麻烦。
  • 另外,我不想使用initialize() 方法,因为它需要客户端调用两个方法而不是一个,如果客户端忘记调用它,它可能容易出错.
【解决方案2】:

你需要模拟 RestTemplate

 @Mock
 RestTemplate restTemplate;

并找到一种方法将其注入您正在测试的类中(通过设置器或构造器)。 由于您使用的是 spring,因此 restTemplate 可能是您应用程序中的一个 bean,并且可以用模拟来替换您的测试上下文。

然后使用 mockito 来模拟你想要的行为:

Mockito.when(restTemplate.get...)
            .thenReturn(something);

【讨论】:

  • 该问题准确地询问了您所说的“找到一种方法...”的问题的答案同样,如果您观察到,@Autowired@Bean 不能使用,因为我们'在这里重新手动创建对象。
  • @GaneshSatpute 来自答案:“将它注入到您正在测试的类中(通过设置器或构造器)”。您的问题归结为依赖注入问题,这是两个选项。
  • @avojak setter 没有问题,因为该类尚未实例化。它不能作为构造函数参数传递的原因是因为它违反了抽象原则。让客户了解内部情况(对于编写测试用例也是如此)不是一个好习惯。
  • @GaneshSatpute 我不同意通过构造函数传递依赖项“不是一个好习惯”——这是基本的依赖注入。
  • 我并不是说通过构造函数传递依赖项不是一个好习惯。我要说的是,在我的特殊情况下,它违反了抽象原则。我班的客户永远不应该担心RestTemplate。创建类的目的是抽象出这些细节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-16
  • 1970-01-01
相关资源
最近更新 更多