Java 如何处理来自 Mocks 的 Setter/Getter-Methods?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33411277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:39:05  来源:igfitidea点击:

How to deal with Setter/Getter-Methods from Mocks?

javaunit-testingtestingmockingmockito

提问by user5417542

So I am still having trouble with the usage of Mockito. So let's assume I have following class (Please ignore the logic, or structure of it, it's just a short example I created from another class, with different names and so on.) :

所以我仍然在使用 Mockito 时遇到问题。所以让我们假设我有以下类(请忽略它的逻辑或结构,这只是我从另一个类创建的一个简短示例,具有不同的名称等等。):

public class Restaurant(
    @Autowired
    private CustomerService customerService;

    private CustomerInputData updateCustomer(CustomerInputData inputData){
        String customerId = inputData.getID();
        Customer customer = customerService.getCustomerById(customerID);
        if(customer.getAddress() != null){
            inputData.setCustomerName(customer.getCustomerName());
            inputData.setCustomerCity(customer.getCustomerCity);
            inputData.setCustomerLanguage(customer.getLanguage);
        }

        return inputData
    }
}

So my understanding of Unit-Tests is, to isolate all dependencies. Here I would have the Customer-class and the Customer-Service.

所以我对单元测试的理解是,隔离所有的依赖。在这里,我将拥有 Customer-class 和 Customer-Service。

So to write a test-class, I would currently do following:

因此,要编写测试类,我目前会执行以下操作:

public class RestaurantTest()
{
    @Mock(name="customerService");
    private CustomerService customerService;

    @InjectMocks
    private Restaurant classUnderTest;

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

    @Test
    public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput(){
        //Some Mocking first
        String customerId = "customerId";
        Customer customer = mock(Customer.class);
        CustomerInputData = mock(CustomerInputData.class);

        doReturn(customer).when(customerService.getCustomerById(any(String.class)));
        doReturn(customerId).when(inputData.getId());

        doReturn("address").when(customer.getAddress());
        doReturn("Name").when(customer.getName());
        doReturn("City").when(customer.getCity());
        doReturn("Language").when(customer.getLanguage());

        doNothing().when(inputData).setCustomerName(any(String.class));
        doNothing().when(inputData).setCustomerCity(any(String.class));
        doNothing().when(inputData).setCustomerLanguage(any(String.class));

        verify(customer.getAddress(), atLeastOnce());
        verify(customer.getName(), atLeastOnce());
        //and so on...

        verify(inputData, atLeastOnce()).setCustomerName(eq("Name"));
        verify(inputData, atLeastOnce()).setCustomerCity(eq("City"));
        verify(inputData, atLeastOnce()).setCustomerLanguage(eq("Language");

    }
}

So currently I have no Assert, I only check if the right methods get called. The reason, why I try to do this like this, and not let the Test-class call the setter/getter is because of isolation. Let's assume inputData.setCustomerCity is broken, my test would fail. So it is depending on the CustomerInputData-Class.

所以目前我没有断言,我只检查是否调用了正确的方法。原因,为什么我尝试这样做,而不是让测试类调用 setter/getter 是因为隔离。假设 inputData.setCustomerCity 损坏,我的测试将失败。所以它取决于 CustomerInputData-Class。

Now how do I approach these getter and setters, what is the best practice?

现在我该如何处理这些 getter 和 setter,最佳实践是什么?

Do I have not understood Mockito good enough? When I use mock(), can I then just use the setter-methods and gethods, and don't need to worry about using doReturns and so on?

我还不够了解 Mockito 吗?当我使用mock()时,我可以只使用setter-methods和gethods,而不必担心使用doReturns等等吗?

I know it is a whitebox-test, I know the methods and what's going on.

我知道这是一个白盒测试,我知道方法和正在发生的事情。

回答by Rogério

That is not the "right" way to go about it, since mocking value objects is widely regarded as a bad practice (it even says so in Mockito's documentation).

这不是解决它的“正确”方法,因为嘲笑值对象被广泛认为是一种不好的做法(它甚至在Mockito 的文档中这样说)。

Your test should instead look like this:

你的测试应该是这样的:

@Test
public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput() {
    String customerId = "customerId";
    String name = "Name";
    String address = "address";
    String language = "language";
    Customer customer = new Customer();
    customer.setName(name);
    customer.setAddress(address);
    customer.setLanguage(language);
    CustomerInputData inputData = new CustomerInputData();
    inputData.setId(customerId);

    doReturn(customer).when(customerService).getCustomerById(customerId);

    CustomerInputData updatedInput = classUnderTest.updateCustomer(inputData);

    assertSame(inputData, updatedInput);
    assertEquals(name, updatedInput.getCustomerName());
    assertEquals(address, updatedInput.getCustomerCity());
    assertEquals(language, updatedInput.getLanguage());
}

For a good presentation on unit testing, see Martin Fowler's recent article.

有关单元测试的精彩介绍,请参阅Martin Fowler 最近的文章

回答by Makoto

Only mock the things which you cannot create or pass through yourself.Do notmock any passed-in entities; providing a fake version is often far superior.

只嘲笑你自己无法创造或通过的东西。难道不是嘲笑任何传入的实体; 提供假版本通常要好得多。

In this scenario, we can get away with a couple of things since we know a few things about our test:

在这种情况下,我们可以避免一些事情,因为我们知道一些关于我们的测试的事情:

  • We are getting back a Customerinstance from the customerService, but we don't need to do any sort of validation on that instance.
  • We haveto mock the customerServiceout since it is an injected dependency.
  • 我们正在从 取回一个Customer实例customerService,但我们不需要对该实例进行任何形式的验证。
  • 我们必须模拟它customerService,因为它是一个注入的依赖项。

In light of these two things, we should mock out CustomerService, which you do kind of successfully - since the field is named the same you don't need the extra metadata in the annotation.

鉴于这两件事,我们应该模拟出CustomerService,你做的有点成功 - 因为该字段的名称相同,你不需要注释中的额外元数据。

@Mock
private CustomerService customerService;

You should also look to use the test runner provided with Mockito so you don't have to explicitly initialize the mocks.

您还应该考虑使用 Mockito 提供的测试运行器,这样您就不必显式初始化模拟。

@RunWith(MockitoJUnitRunner.class)
public class RestaurantTest {
    // tests
}

Now, on to the actual unit test. The only thing that really sucks is that you have to provide an instance of a Customer to use, but outside of that, it's not too bad. Our givens are the instance of CustomerDatawe want to mutate, and the Customerwe're providing for test. We then have to simply assert the values that we care about are coming back for our test CustomerDatainstance.

现在,进入实际的单元测试。唯一真正糟糕的是你必须提供一个 Customer 的实例来使用,但除此之外,这还不算太糟糕。我们的给出是CustomerData我们想要变异的实例,我们Customer提供测试。然后,我们必须简单地断言我们关心的值将返回到我们的测试CustomerData实例中。

@Test
public void updateCustomer_WithValidInput_ShouldReturnUpdatedInput(){
    //given
    final Customer customer = new Customer();
    customer.setId("123");
    customer.setAddress("Addr1");
    customer.setName("Bob");
    customer.setCity("St8")
    customer.setLanguage("Java");

    final CustomerInputData inputData = new CustomerInputData();
    inputData.setId(customer.getId());

    //when
    when(customerService.getCustomerById(customer.getId())).thenReturn(customer);
    classUnderTest.updateCustomer(customerData);

    //then
    verify(customerService.getCustomerById("123"));
    assertThat(customerData.getCustomerName(), equalTo(customer.getName()))
    // and so forth
}