Issue
Issue with unit test is that the same collection is processed differently in a stream and in a for loop.
The collection in both cases is empty (data.size() = 0)
, but in the Case 1 that collection is somehow processed, in other words it will step in the for-loop.
In Case 2, that collection is just skipped (which is expected since it's empty).
Tests are using Mockito, and Result<Record>
is comming for JOOQ.
The tests are old and unchanged, the only change is going from for-loop to stream.
Case 1
private SearchResult iterateData(
Result<Record> data, ...) {
for (Record record : data) {
doSomething(record);
}
Case 2
private SearchResult iterateData(
Result<Record> data, ...) {
data.stream().forEach(record -> doSomething(record));
Screenshot of Case 1 for loop example
Mocked Result object
private DefaultSearchRequestModel rowSpecificValuesTestSetup(
parameters...) {
DefaultSearchRequestModel searchRequest = new DefaultSearchRequestModel(
Arrays.asList(....),
Collections.singletonList(
new SearchFilter(
"test",
Collections.singletonList(...)));
List<Column> columns =
this.searchService.filterUserAllowedColumns(...);
Condition searchCondition =
this.searchRepositoryMock.getSearchConditions(...);
List<TableJoinMapping> joinMappings = ColumnHelper.getColumnTranslateDeviceJoinMappings(
columns,
searchRequest.getFilters());
Result<Record> deviceDataResultMock = Mockito.mock(Result.class);
Iterator<Record> resultIterator = Mockito.mock(Iterator.class);
final Table fromTableMock = Mockito.mock(Table.class);
when(resultIterator.hasNext()).thenReturn(true, false);
Record recordMock = Mockito.mock(Record.class);
when(resultIterator.next()).thenReturn(recordMock);
when(deviceDataResultMock.iterator()).thenReturn(resultIterator);
when(recordMock.get(CONTRACTID)).thenReturn(contractId);
...
when(this.userPermissions.getAccessPermissions()).thenReturn(searchRequest.getColumns().stream().map
(name -> Column.findByName(name).getId()).collect(
Collectors.toList()));
when(this.searchRepositoryMock.getCurrentTable(companyId))
.thenReturn(fromTableMock);
when(recordMock.get(TYPEID)).thenReturn(financialTypeId);
when(this.searchRepositoryMock.getDeviceData(
ArgumentMatchers.anyList(),
ArgumentMatchers.anyList(),
any(),
any(),
eq(searchRequest.getPageSize()),
eq(searchRequest.getPage()),
eq(searchRequest.getSortCriterias()),
eq(fromTableMock),
ArgumentMatchers.anyList(),
eq(Optional.empty()),
eq(this.dslContextMock)))
.thenReturn(deviceDataResultMock);
return searchRequest;
}```
Solution
Why it didn't work
You're mocking Result.iterator()
:
when(deviceDataResultMock.iterator()).thenReturn(resultIterator);
But you didn't mock Result.spliterator()
, or at least I didn't see it, which is what's being called by Result.stream()
, which is just Collection.stream()
:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
So, you'll have to mock the spliterator()
method as well, and probably a few others, too! Alternatively, tell Mockito to call default
methods:
Can you make mockito (1.10.17) work with default methods in interfaces?
A comment on mocking in general
I'm not convinced that mocking the jOOQ API is a very good idea. The jOOQ API is vast, and you'll likely forget to mock this or that method as this question here has aptly shown. With your current setup, you're planning on updating your mock every time you project a new column? E.g. you're doing this:
when(recordMock.get(DEVICEID.getName()).thenReturn(deviceId);
What if the column is renamed? Or another column is projected? You'll have to update this test. That feels like quite the chore, and it's very error prone.
While jOOQ itself has JDBC mocking capabilities, please consider the bold disclaimer on that manual page:
Disclaimer: The general idea of mocking a JDBC connection with this jOOQ API is to provide quick workarounds, injection points, etc. using a very simple JDBC abstraction. It is NOT RECOMMENDED to emulate an entire database (including complex state transitions, transactions, locking, etc.) using this mock API. Once you have this requirement, please consider using an actual database product instead for integration testing, rather than implementing your test database inside of a MockDataProvider
When working with databases, it's usually best to resort to running integration tests, see the following resources for some details:
- Integration testing with Jooq
- Using H2 as a Test Database Product with jOOQ
- How to Integration Test Stored Procedures with jOOQ
- Using Testcontainers to Generate jOOQ Code
Of course, you can write a few smoke tests to ensure jOOQ works correctly if you don't trust jOOQ (jOOQ being an external dependency). But the jOOQ unit and integration tests are vast, so in general, you should be able to trust core types like Result
or Record
to do the right thing for you. What you really want to test is your query correctness, and that, you can only integration test against an actual database instance.
Answered By - Lukas Eder
Answer Checked By - Senaida (JavaFixing Volunteer)