简体   繁体   中英

How to use ArgumentCaptor in mockito

I'm learning Mockito and Unit Testing in general. I want to learn how to unit test better by using Argument Captor. I'm using jdbc to handle my SQL statement. I have a method that inserts a user into my DB.

public void insert(User user) {
   String sql = "INSERT INTO user (id) VALUES ?";
   jdbcTemplate.update(new PreparedStatementCreator() {
     @Override
     public PreparedStatement createPreparedStatement(Connection connection) {
       final PreparedStatement ps = connection.prepareStatement(sql);
       ps.setString(1, user.getId().trim());
       return ps;
     }
   });
 }

Below is the test that I'm trying to write with ArgumentCaptor.

    @Test
      public void testInsert() {
        User user = new User("testID");
        ArgumentCaptor<PreparedStatementCreator> captor = ArgumentCaptor.forClass(PreparedStatementCreator.class);
        insert(user);
        verify(mockJdbc, times(1)).update(captor.capture());
        PreparedStatementCreator actual = captor.getValue();
        assertEquals(??, actual.createPreparedStatement(??));
      }

Any advice or insight on what should be in the '??'for the assert statement or if this is the correct way to use Argument Captor?

Thank You

Edit:

      @Test
      public void testInsert() throws SQLException {
            ArgumentCaptor<PreparedStatementCreator> captor = ArgumentCaptor.forClass(PreparedStatementCreator.class);
            PreparedStatement ps = mockConnection.prepareStatement("INSERT INTO user (id) VALUES ?";);
            ps.setString(1, user.getId().trim());
            insert(user);
            verify(mockJdbcTemplate, times(1)).update(captor.capture());
            PreparedStatementCreator actual = captor.getValue();
            assertEquals(ps, actual.createPreparedStatement(mockConnection));
          }

I like your approach of using ArgumentCaptor s.

You are using the ArgumentCaptor correctly to capture the argument of the method update on the mocked JDBC template; however, you cannot extract the argument used to call the PreparedStatementCreator , because this is object is not a mock.

Conceptually, the difficulty you have to test this part of your code comes from the fact you don't control the creation of the PreparedStatementCreator . One possible solution would be to take back control on how and when you create these objects; so as to allow you to mock them in your tests.

Following a standard creational pattern , you could introduce a factory which (single) responsibility is to create PreparedStatementCreator .

interface PreparedStatementCreatorFactory {

  PreparedStatementCreator newPreparedStatementCreator(Connection connection, String sql, User user);

}

public final class DefaultPreparedStatementCreatorFactory {
  @Override
  public PreparedStatementCreator newPreparedStatementCreator(Connection connection, String sql, User user) {
    final PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1, user.getId().trim());
    return ps;
  }
}

Then, in the class you are testing (which contains the JDBC mock), you can inject a mock of the PreparedStatementCreatorFactory . Then, instead of capturing the argument of the JDBC mock, you can capture the argument on the factory instead; and, of course, specify what the mocked factory returns.

PreparedStatementCreatorFactory factory = Mockito.mock(PreparedStatementCreatorFactory.class);
PreparedStatementCreator creator = Mockito.mock(PreparedStatementCreator.class);
// Mock the creator at your convenience.

when(factory.newPreparedStatementCreator(any(Connection.class), any(String.class), any(User.class)).thenReturn(creator);

...

User user = new User("testID");
ArgumentCaptor<Connection> connectionCaptor = ArgumentCaptor.forClass(Connector.class);
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);

insert(user);

verify(factory, times(1)).newPreparedStatementCreator(connectionCaptor.capture(), sqlCaptor.capture(), userCaptor.capture());

assertEquals(user, userCaptor.getValue());

One drawback of this approach is that it adds one level of indirection and relative complexity; the main advantage is, as we see, to improve the separation of concerns in your design and in fine the testability of your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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