Ray Ray - 1 month ago 10
Java Question

Using Mockito and Java 8 streams, how do you ensure a filter clause is working?

Say I've got this code:

public void doSomething(Collection<Item> batch) {
batch.stream()
.filter(item -> item.group_id == 1)
.forEach(this::processItem);
}


processItem()
is private method.

In real life the filter is more complex. I want to construct a unit test to verify a Collection items that all should fail the filter check, will result in
forEach()
never being invoked.

2 caveats:


  • Cannot modify
    doSomething()
    method signature

  • Prefer to keep
    processItem()
    private void method


Answer Source

You could make the consumer invoked by forEach() a settable field:

private Consumer<Item> processingAction = this::processItem;

public void setProcessingAction(Consumer<Item> action) {
   this.processingAction = action;
}

public void doSomething(Collection<Item> batch)  { 
   batch.stream()      
   .filter(item -> item.group_id == 1)      
   .forEach(processingAction);
}

Now your test can be:

objUnderTest.setProcessingAction( item -> assertThat(item.groupId, is(1)));
objUnderTest.doSomething(list);

No need for Mockito here - your test-double is a lambda. You can, of course, mock a Consumer<Item> if you prefer.

It's a bit of a smell having a method just for testing (setProcessingAction()) - but that's because of the hard-coding and tight-coupling you've chosen to do. Why not take the opportunity to make injection (possibly constructor-injection) the primary way of setting the action?

If you're unwilling to make the action injectable, then you're stuck with processItem() always being called, and the effects of processItem() will be the only way to know what items were filtered.