Trust is good control is better – JUnit Tests (parametrized and different arguments providers) with RxJava and some Mockito

A follow up to Software tests – some basic JUnit testing in the area of RxJava. Not much text right now, just the announcement, the code below and the link to my github repo. Includes a build.gradle with all the needed dependencies.
(There are already some other test concerning ConnectableObservables subscriptions and time of emit.)

Short summary:
How to test classes using RxJava Observables with JUnit. Description in the Java file as comment.

  • TestObserver assertions
  • mocking with Mockito
  • mocks returning Observables from Observable.just(…) and BehaviorSubject
  • parametrized tests with value sources annotation, method and AgrumentProvider
package rxjava;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.stream.Stream;

import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class ObservableMockingTest {
    @Mock ValueProvider valueProvider;
    ValueProcessor valueProcessor;

    @BeforeEach
    void setUp(){
        valueProcessor = new ValueProcessor(valueProvider);
    }

    @Test
    void value8_emits64(){
        // when testing only one or a fixed list of emitted values is enough, mocking with Observable.just(...values)
        // is sufficient
        when(valueProvider.values()).thenReturn(Observable.just(8));

        valueProcessor.processedValue().test().assertValue(i -> i == 64);
    }

    @Test
    void values_multiple_emitsProcessed(){
        // when testing multiple values, transitions or timing of the emit - I like to use BehaviorSubjects
        BehaviorSubject<Integer> values = BehaviorSubject.create();
        when(valueProvider.values()).thenReturn(values);

        values.onNext(8);

        valueProcessor.processedValue().test().assertValue(i -> i == 64);

        values.onNext(10);

        valueProcessor.processedValue().test().assertValue(i -> i == 100);
    }

    // =============================== parametrized tests

    // Very useful if you want to test a class with a set of values
    // I will list some possibilities how to achieve your test goal

    @ParameterizedTest
    @ValueSource(ints = {8, 10, 25})
    void values_valueSource_emitsProcessed(int value){
        // simple, but very limited
        when(valueProvider.values()).thenReturn(Observable.just(value));

        valueProcessor.processedValue().test().assertValue(i -> i == value*value);
    }

    static Stream<Arguments> yourTestProviderMethodName(){
        // Arguments allow every and multiple types - but it has to match the test method signature
//        return Stream.of(arguments(true, "NOPE", 8, NullPointerException.class, MyEnum.VALUE_OFF), ...);
        return Stream.of(arguments(8), arguments(10), arguments(25));
    }
    @ParameterizedTest
    @MethodSource(value = "yourTestProviderMethodName")
    void values_test_methodSource(int value){
        // When using MethodSource you have to provide the name of the method providing the test parameters
        // This is error prone because misspelling or renaming the method breaks this test.
        // Can get a little messy when you have multiple similar named provider methods
        // For this case definitely too much - just want to show
        when(valueProvider.values()).thenReturn(Observable.just(value));

        valueProcessor.processedValue().test().assertValue(i -> i == value*value);
    }

    static class YourTestArgumentProviderClass implements ArgumentsProvider{
        // Instead of just a provider method you can implement your ArgumentsProvider class. provideArguments is called
        // and you logic returning the desired test values has to be in there
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
            return Stream.of(arguments(8), arguments(10), arguments(25));
        }
    }

    @ParameterizedTest
    @ArgumentsSource(YourTestArgumentProviderClass.class)
    void values_test_argumentProvider(int value){
        // Using an ArgumentsProvider removes relying on the matching method name String and shows clearly what the
        // provider is instead of just another method whose name you have to copy and search in the test. Using produces
        // some (automatically generated) boilerplate code, but makes it much clearer as it is defined as an
        // ArgumentsProvider and the structure is uniform. Plus you can STRG+click -> jump there from the test
        //
        // I learned this when using @Nested class in a test and was not able to define a MethodSource in a nested
        // test class, but was able to use an ArgumentsProvider instead

        when(valueProvider.values()).thenReturn(Observable.just(value));

        valueProcessor.processedValue().test().assertValue(i -> i == value*value);
    }
}

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.