Skip to content

Instantly share code, notes, and snippets.

@jonjack
Last active November 15, 2018 11:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonjack/7b2285c88da04b43df148ea5c4f1d34f to your computer and use it in GitHub Desktop.
Save jonjack/7b2285c88da04b43df148ea5c4f1d34f to your computer and use it in GitHub Desktop.

We have a method to test which iterates over some List. The example is a validator service which iterates over a List of validators applying each one and returning when one fails ie. it returns an Optional containing an error message (this is just a concrete example to demonstrate Mockito and is not important to understand).

public class SomeValidationService implements ValidationService {

    /* List of validators which all encapsulate some different business validation logic */
    /* This gets injected say via Spring */
    private List<Validator> validators;
 
    public Optional<ErrorMessage> validateAction(final ObjectToValidate obj) {
        for (Validator validator: validators) {
            Optional failure = validator.validate(obj);
            if (failure.isPresent()) {
                return failure;
            }
        }
        return Optional.empty();
    }
}

And of course we have a Validator interface and some implementations.

public interface Validator {
    Optional<ErrorMessage> validate(ObjectToValidate object);
}

public class ValidatorOne implements Validator {
    @Override
    public Optional<ErrorMessage> validate(final ObjectToValidate object) { ... }
}

public class ValidatoTwo implements Validator {
    @Override
    public Optional<ErrorMessage> validate(final ObjectToValidate object) { ... }
}

How do we mock the List with Mockito?

I originally tried to Mock just the List<Validator> but this fails in the for loop. It turns out that we have to mock and inject the Iterator that the loop must use as well. This is achieved as below.

@RunWith(MockitoJUnitRunner.class)
public class SomeValidationServiceTest {

    @Mock
    private ValidatorOne validatorOne;

    @Mock
    private ValidatorTwo validatorTwo;

    @Mock
    private final List<Validator> validators = new ArrayList<Validator>() { {
        add(validatorOne);
        add(validatorTwo);
    }};

    @Mock
    private Iterator<Validator> validatorIterator;
    
    @Mock
    ObjectToValidate objectToValidate
    
    // Inject the above Mocks into the class under test
    @InjectMocks
    private SomeValidationService validationService;
    
    private static Optional<ErrorMessage> successfulValidation = Optional.empty();

    private static Optional<ErrorMessage> validationOneFailure = Optional.of(new ErrorMessage(...));
    
    private static Optional<ErrorMessage> validationTwoFailiure = Optional.of(new ErrorMessage(...));

    @Before
    public void setUp() {
    
        // We have to mock what happens when the for loop internally looks up the iterator
        Mockito.when(validators.iterator()).thenReturn(validatorIterator);
    
        // We have to mock what we want to happen when the for loop calls the next and hasNext 
        // operations of the Iterator
        Mockito.when(validatorIterator.hasNext()).thenReturn(true, true false);
        Mockito.when(validatorIterator.next())
                .thenReturn(validatorOne)
                .thenReturn(validatorTwo);

        // Setup the success cases as the defaults
        given(validatorOne.validate(objectToValidate)).willReturn(successfulValidation);
        given(validatorTwo.validate(objectToValidate)).willReturn(successfulValidation);
    }
    
    @Test
    public void testValidationSuccess() {
        Optional<ErrorMessage> validation = validationService.validateAction(objectToValidate);
        assertThat(validation).isEqualTo(Optional.empty());
    }

    @Test
    public void testValidationOneFailure() {
        // override the success case with a failure case for validator One
        given(validatorOne.validate(objectToValidate)).willReturn(validationOneFailure);
        Optional<ErrorMessage> validation = validationService.validateAction(objectToValidate);
        assertThat(failure.isPresent()).isTrue();
        assertThat(failure.get()).isInstanceOf(ErrorMessage.class);
        assertThat(failure.get().getMessageId()).isEqualTo(VALIDATION_ONE_FAILURE_MESSAGE_ID);
    }
    
    @Test
    public void testValidationTwoFailure() {
        // override the success case with a failure for validator Two
        given(validatorTwo.validate(objectToValidate)).willReturn(validationTwoFailure);
        Optional<ErrorMessage> validation = validationService.validateAction(objectToValidate);
        assertThat(failure.isPresent()).isTrue();
        assertThat(failure.get()).isInstanceOf(ErrorMessage.class);
        assertThat(failure.get().getMessageId()).isEqualTo(VALIDATION_TWO_FAILURE_MESSAGE_ID);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment