Home > Enterprise >  Java Mockito useConstructor withSettings, error Invalid use of argument matchers
Java Mockito useConstructor withSettings, error Invalid use of argument matchers

Time:10-21

I am trying to Mock this following code with transaction manager. Receiving error below. How can I resolve this?

Code:

DefaultTransactionDefinition paramTransactionDefinition = new DefaultTransactionDefinition();
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource());
TransactionStatus status = transactionManager.getTransaction(paramTransactionDefinition);
  

Test:

@Mock
private JdbcTemplate jdbcTemplate;
@Mock
private PlatformTransactionManager platformTransactionManager;
@Mock
private DataSource dataSource;
@Mock
private TransactionStatus transactionStatus;

given(namedParameterJdbcTemplate.getJdbcTemplate()).willAnswer(a -> jdbcTemplate);
given(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource()).willAnswer(a -> dataSource);
platformTransactionManager = Mockito.mock(DataSourceTransactionManager.class, withSettings().useConstructor(dataSource));

given(platformTransactionManager.getTransaction(any())).willAnswer(a -> transactionStatus);

Error:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at         given(platformTransactionManager.getTransaction(any())).willAnswer(a -> transactionStatus);


This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");

trying to use this resource: https://stackoverflow.com/a/60429929/15435022

CodePudding user response:

I think you are taking things a bit too eagerly while mocking. Given should look like this:

//method with parameter
given(aMock.aSingleCall(anArgumentMatcher)).willReturn(aValue);
//method without parameter
given(aMock.aSingleCall()).willReturn(aValue);

The chained call you are using here:

given(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource()).willAnswer(a -> dataSource);

Is supposed to be split into two pieces, like this:

given(namedParameterJdbcTemplate.getJdbcTemplate()).willReturn(jdbcTemplate);
given(jdbcTemplate.getDataSource()).willReturn(dataSource);

On the constructor call, you are facing the issue of not actually using the mock while stubbing. Mockito will not be able to magically replace your constructor call with a mocking (and it shouldn't either). You should be simply using something like one of the following scenarios.

A) If you want to mock only the dataSource

Change your code in a way you can set the dataSource mock you have like this:

PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);

It can be through a constructor or setter, does not matter.

B) If you want to mock the transactionManager as well

Change your code to avoid directly calling the constructor, accept the transactionManager as a dependency and set the mock you have already created (the field named platformTransactionManager).

Note: Calling any(aClass) will return the default value of the type when the any(aClass) is executed. In your case, this is a null so when you are calling the real constructor with it, you are just writing new DataSourceTransactionManager(null) with extra steps.

Update#1: how do I "Change your code in a way"?

It depends on the rest of your code, but assuming that this is in a single method:

DefaultTransactionDefinition paramTransactionDefinition = new DefaultTransactionDefinition();
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource());
TransactionStatus status = transactionManager.getTransaction(paramTransactionDefinition);

you could either use a parameter of PlatformTransactionManager transactionManager and avoid the constructor call (extract parameter) or you could at least extract the constructor call to a method and you can use a Mockito spy to make that method return a mock instead of the real object.

CodePudding user response:

This will work:

@Mock
private JdbcTemplate jdbcTemplate;
@Mock
private PlatformTransactionManager platformTransactionManager;
@Mock
private DataSource dataSource;
@Mock
private TransactionStatus transactionStatus;

given(namedParameterJdbcTemplate.getJdbcTemplate()).willAnswer(a -> jdbcTemplate);
given(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource()).willAnswer(a -> dataSource);

MockedConstruction<DataSourceTransactionManager> mocked = Mockito.mockConstruction(DataSourceTransactionManager.class,
        (mock, context) -> {
            when(mock.getTransaction(any())).thenReturn(null);
        });
given(platformTransactionManager.getTransaction(new DefaultTransactionDefinition())).willAnswer(a -> transactionStatus);
mocked.close();

Resource: https://rieckpil.de/mock-java-constructors-and-their-object-creation-with-mockito/

  • Related