如何使用mockito验证使用此参数或其他参数调用特定方法?

How to verify that a specific method was called with this parameter or another parameter using mockito?

提问人:obela06 提问时间:8/15/2023 更新时间:8/29/2023 访问量:43

问:

如何使用 mockito 的 verify 方法验证我的 sendMessage 方法是否使用 Msg.SAVE 或 Msg.UPDATE 调用。

@Service
public class CustomerService {
private final CustomerRepository customerRepository;
private final SendMessageInTopic messageInTopic;
public CustomerService (CustomerRepository customerRepository,  SendMessageInTopic messageInTopic) 
 {
    this.customerRepository = customerRepository;
    this.messageInTopic= messageInTopic;
 }

public Customer saveCustomer(Customer savedCustomer) {
        Customer customer =    customerRepository
                .findCustomerByEmail(savedCustomer.getEmail());

        if(customer != null) {
            customer.setFirstName(savedCustomer.getFirstName());
            customer.setLastName(savedCustomer.getLastName());
            customer.setPhoneNumber(savedCustomer.getPhoneNumber());
            messageInTopic.sendMessage(savedCustomer.getId(),Msg.SAVE)
        } else {
            customer = new Customer();
            customer.setFirstName(savedCustomer.getFirstName());
            customer.setLastName(savedCustomer.getLastName());
            customer.setEmail(savedCustomer.getEmail());
            customer.setPhoneNumber(savedCustomer.getPhoneNumber());
            messageInTopic.sendMessage(savedCustomer.getId(),Msg.Update)
        }
        return customerRepository.save(customer);
    }
}

public class CustomerServiceTest{
private final CustomerRepository customerRepository = mock(CustomerRepository.class);
private final CustomerService customerService = mock(CustomerService.class);
private final SendMessageInTopic  messageInTopic = mock(SendMessageInTopic.class);
private final CustomerMapper customerMapper = mock(CustomerMapper.class);
    @Test
    void whenSaveCustomerThenMsgSAVEOrUPDATE() {
    // Given

    final var customerId = "123456";
    final var customer =
        new CustomerDto(ensUserId, 'james', 'GREWAN', '32766666', '[email protected]');

    final customerEntity = mock(Customer.class);

    when(customerRepository.findCustomerByEmail(customer.getEmail())
        .thenReturn(Optional.of(customerEntity));
    when(customerRepository.save(customerEntity)).thenReturn(customerEntity);
    when(customerMapper.toDto(customerEntity)).thenReturn(customer);

    // When

    final var result = customrService.saveCustomer(customer);

    // Then

    assertEquals(result, customer);
    verify(messageInTopic, times(1)).sendMessage(result.getId(),Msg.SAVE);
  }
}

verify(messageInTopic, times(1)).sendMessage(result.getId(),Msg.SAVE);:在这里,我将测试我是在创建情况下使用 Msg.SAVE 参数还是在更新情况下使用 Msg.UPDATE 参数调用我的 sendMessage 方法。

请问如何做这个测试?

java 单元测试 junit mockito tdd

评论

0赞 Jorn 8/15/2023
可用于匹配特定参数值Matchers.eq
0赞 obela06 8/15/2023
嗨,@Jorn,你能给我举个例子来解释我如何使用我的两个参数 SAVE 或 UPDATE 来使用 Matchers.eq 吗?
0赞 Jorn 8/15/2023
谷歌上有很多这样的例子
0赞 obela06 8/15/2023
@Jorn,可以做这个例子: verify( messageInTopic, times(1))sendMessage(result.getId(),eq( Msg.SAVE),eq( Msg.UPDATE));我的 sendMessage 方法必须有两个参数,id 和字符串 Msg.SAVE 或 Msg.UPDATE。
1赞 knittl 8/15/2023
你在嘲笑一切,甚至是你的服务!你到底在测试什么?没错,除了你的模拟设置之外什么都没有。你的真实逻辑永远不会被执行(因此也永远不会被调用)CustomerService.saveCustomersendMessage

答:

0赞 Keji C 8/16/2023 #1

@knittl说得对。您的验证调用是否正确。问题是你在嘲笑你试图测试的类。当您调用 时,Mockito 实际上是在创建一个存根所有方法的类(即覆盖它们并返回默认值):mock(CustomerService.class)

class CustomerServiceMock extends CustomerService {
  ...
  @Override
  public Customer saveCustomer(Customer savedCustomer) {
    // Stub
    return null;
  }
  ...
}

所以将返回 null,因为 customerService 实际上是一个 CustomerServiceMock。 会因此而失败。解决方法是将 替换为 .请注意,customerRepository 和 messageInTopic 是您声明的模拟。customerService.saveCustomer(customer)assertEquals(result, customer)customerService = mock(CustomerService.class);customerService = new CustomerService(customerRepository, messageInTopic)

但是,由于这些行,您的 assertEqual 仍然应该失败。

when(customerRepository.findCustomerByEmail(customer.getEmail())
        .thenReturn(Optional.of(customerEntity));
when(customerRepository.save(customerEntity)).thenReturn(customerEntity);

这些使得返回 mock .saveCustomer(customer)customerEntity

  1. 失败,因为不匹配。assertEqualscustomerEntitycustomer
  2. 也不起作用,因为它等价于 .这是因为 是一个 ,这意味着它看起来像这样:verify(messageInTopic ...sendMessage(result.getId(),Msg.SAVE)sendMessage(null, Msg.SAVE)customerEntitymock(Customer.class)customerEntity.getId
class CustomerMock extends Customer {
...

  public String getId() {
    // Stub
    return null;
  }
...
}

有很多方法可以解决这个问题。我的建议是像这样删除和使用:customerEntitycustomer

when(customerRepository.findCustomerByEmail(customer.getEmail())
        .thenReturn(Optional.of(customer));
when(customerRepository.save(customer)).thenReturn(customer);

这将使 saveCustomer 返回,以便 assertEqual 通过。它也会使它变得平等。 无论如何都应该返回真正的客户,所以这对我来说最有意义。customersavedCustomer.getId()result.getId()findCustomerByEmail


此外,仅供参考:Matchers.eq 和直接使用参数在功能上是相同的,因此它不会更改任何内容:

verify(messageInTopic, times(1)).sendMessage(result.getId(),Msg.SAVE);
verify(messageInTopic, times(1)).sendMessage(Matchers.eq(result.getId()), Matchers.eq(Msg.SAVE));

那么为什么要使用 Matchers.eq 呢?好吧,假设您只想验证 sendMessage 是否使用 Msg.SAVE 与 Msg.UPDATE 调用,并且您不关心 id。问题是你不能混合匹配器和直接参数,所以这不起作用:

verify(messageInTopic, times(1)).sendMessage(Matchers.any(), Msg.SAVE);

但这会起作用:

verify(messageInTopic, times(1)).sendMessage(Matchers.any(), Matchers.eq(Msg.SAVE));

此外,作为第二个参考,有许多不同的方法可以验证参数。这是一篇关于所有不同方式的信息的帖子:Mockito。验证方法参数。如果要验证作为对象的参数,这一点尤其重要。

0赞 Eduardo Yáñez Parareda 8/29/2023 #2

有几种方法可以做到这一点。例如,使用捕获器:

首先在测试类中声明要检查的参数的捕获器:

@Captor
private ArgumentCaptor<Msg.class> msgCaptor;

然后在测试中,验证并断言正确的值:

verify(messageInTopic).sendMessage(result.getId(), msgCaptor.capture());
// If using org.assertj.core.api.Assertions.assertThat;
assertThat(msgCaptor.getValue()).isEqualTo(Msg.SAVE);