简体   繁体   English

使用argumentsCaptor测试

[英]Testing with argumentCaptor

Im using ArgumentCaptor to capture the internal call of PreparedStatementCreator. 我正在使用ArgumentCaptor捕获PreparedStatementCreator的内部调用。

public void update(Item item) {
   String sql = "SET NAME = ? where ID = ?";
   jdbcTemplate.update(new PreparedStatementCreator() {
     @Override
     public PreparedStatement createPreparedStatement(Connection connection) {
       final PreparedStatement ps = connection.prepareStatement(sql);
       ps.setString(1, item.getName());
       ps.setInt(1, item.getId());
     }
   });
 }

Below is the test with ArgumentCaptor. 以下是ArgumentCaptor的测试。

      @Test
      public void testUpdate() {
        Item item= new Item("1","testName");
        ArgumentCaptor<PreparedStatementCreator> pscArgCaptor = ArgumentCaptor.forClass(PreparedStatementCreator.class);
        insert(item);
        verify(mockJdbc, times(1)).update(pscArgCaptor .capture());
        assertNotNull(pscArgCaptor.getValue(); 
        assertEquals(pscArgCaptor , ?);
      }

I can successful capture the PreparedStatementCreator call when I call update. 调用update时,我可以成功捕获PreparedStatementCreator调用。 I used assertNotNull to test and see if the pscArgcaptor was something. 我使用assertNotNull进行测试,看看pscArgcaptor是否有用。 I dont know how to get inside this object to verify my parameters in the prepared statement such as ps.setString(1, item.getId() and ps.setString(1, item.getName() to ensure that the prepared statement is the correct one. Is it possible to do without any getters from PreparedStatementCreator? 我不知道如何进入该对象以验证我在准备好的语句中的参数,例如ps.setString(1, item.getId()ps.setString(1, item.getName()以确保准备好的语句是是正确的,是否有可能没有PreparedStatementCreator的任何获取方法?

The problem here is that your code under test instantiates its dependencies (here the instance of PreparedStatementCreator ) itself. 这里的问题是,您的被测试代码实例化了其依赖关系(这里是PreparedStatementCreator的实例)。

Instead you should inject an instance of it. 相反,您应该注入它的一个实例。 In that case you could inject a mock of PreparedStatementCreator and capture the parameters passed to that mock. 在那种情况下,您可以注入PreparedStatementCreator的模拟并捕获传递给该模拟的参数。


I'm fairly new to Junit and Mocking. 我是Junit和Mocking的新手。

This is not so much about mocking but about Single Responsibility/Separation of Concerns . 这不仅仅是关于嘲笑,而是关于单一责任/关注分离 It improves reusability of your code. 它提高了代码的可重用性。 Testability is a sign of reusable code. 可测试性是可重用代码的标志。

What do you mean by inject a mock. 您注入模拟是什么意思。 Could you provide me an example? 你能给我一个例子吗?

the problem here is that the interface PreparedStatementCreator does not provide a suitable interface to be used with the Item class as a parameter. 这里的问题是接口PreparedStatementCreator没有提供与Item类一起用作参数的合适接口。 Therefore it is usefull to introduce a factory class : 因此,引入工厂类是有用的:

public class ItemPreparedStatementCreatorFactory{
   public PreparedStatementCreator createFor(Item item){
     return new PreparedStatementCreator() {
        @Override
         public PreparedStatement createPreparedStatement(Connection connection) {
           final PreparedStatement ps = connection.prepareStatement( "SET NAME = ? where ID = ?");
           ps.setString(1, item.getName());
           ps.setInt(1, item.getId());
           return ps;
         }
       })
    }
}

You would pass an instance of that class as constructor parameter to your code under test: 您可以将该类的实例作为构造函数参数传递给要测试的代码:

Your code under test could look like this: 您的测试代码可能如下所示:

public class YourDaoClass { 
  private final JdbcTemplate jdbcTemplate;
  private final ItemPreparedStatementCreatorFactory preparedStatementCreatorFactory;
  public YourDaoClass(ItemPreparedStatementCreatorFactory preparedStatementCreatorFactory, JdbcTemplate jdbcTemplate){
    this.preparedStatementCreatorFactory = preparedStatementCreatorFactory;
    this.jdbcTemplate = jdbcTemplate;
  }

Then the method under test would change to: 然后,被测方法将变为:

public void update(Item item) {
  jdbcTemplate.update(preparedStatementCreatorFactory.createFor(item));
}

And you would have separate tests for your code under test. 并且您将对要测试的代码进行单独的测试。

public class YourDaoClassTest{
    @Rule
    public MockitoRule rule = MockitoJUnit.rule();
    @Mock
    private JdbcTemplate jdbcTemplate;
    @Mock
    private ItemPreparedStatementCreatorFactory preparedStatementCreatorFactory;
    @Mock
    private PreparedStatementCreator preparedStatementCreator;

    YourDaoClass yourDaoClass;


    @Before
    public void setup(){
      // I prefer direct object creation over @InjectMocks since the latter does not raise compile errors on missing constructor arguments...
      yourDaoClass = new YourDaoClass(preparedStatementCreatorFactory,jdbcTemplate); 
    }  


    @Test
    public void passesItemToStatementFactory(){
       Item item = new Item();
       doReturn(preparedStatementCreator)
            .when(preparedStatementCreatorFactory)
            .createFor(item);

       yourDaoClass-update(item);

       InOrder inOrder= inOrder(preparedStatementCreatorFactory,jdbcTemplate);
       inOrder.verify(preparedStatementCreatorFactory).createFor(item);
       inOrder.verify(jdbcTemplate).update(preparedStatementCreator);
    }
}

public class ItemPreparedStatementCreatorFactoryTest{
    @Rule
    public MockitoRule rule = MockitoJUnit.rule();
    @Mock
    private PreparedStatement preparedStatement;
    @Mock
    private Connection connection;

    @Before
    public void setup(){
      // maybe exchange anyString() with an ArgumentCaptor
       doReturn(preparedStatement).when(connection).prepareStatement(anyString());
    }

    @Test
    public void passesNameAndIdToPreparedStatement(){
       Item item = new Item();
       item.setName("an valid name");
       item.setID(ANY_VALID_ID);

       ItemPreparedStatementCreatorFactory itemPreparedStatementCreatorFactory =
           new ItemPreparedStatementCreatorFactory();
       PreparedStatement createdPreparedStatement = itemPreparedStatementCreatorFactory.createFor(item);

       verify(createdPreparedStatement).setString(1, item.getName());
       verify(createdPreparedStatement).setInt(1, item.getId());
   }
}

conclusion 结论

When ever you have difficulties to test your production code it most likely is not written in an reusable way violating the SRP/SoC principles. 每当您遇到测试生产代码的困难时,很可能都不会违反SRP / SoC原则以可重用的方式编写代码。

On the other hand the tests shown are dumb because there is no real logic to verify because the production code is "too simple to fail" and the tests basically repeat what the code does. 另一方面,显示的测试是愚蠢的,因为没有真正的逻辑可以验证,因为生产代码“太容易失败”,并且测试基本上重复了代码所做的工作。 Usually such tests are not really useful since they are to tightly coupled with the implementation and break when the implementation changes. 通常,此类测试并不是真正有用的,因为它们会与实现紧密结合,并在实现发生更改时中断。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM