Schlagwort: testing

#6 One thing to look out for when using LiveData and Transformations

If you were to unit test LiveData and LiveData transformations you might write a test like this:

class MainViewModel(initialArticle: Article) : ViewModel() {

    private val _articleLiveData = MutableLiveData<Article>()
    val articleLiveData: LiveData<Article> = _articleLiveData
    val articleTitleLiveData: LiveData<String> = Transformations.map(_articleLiveData) { it.title }

    init {
        _articleLiveData.value = initialArticle
    }
}

class MainViewModelTest {

    @get:Rule
    var rule: TestRule = InstantTaskExecutorRule()

    private lateinit var viewModel: MainViewModel

    private val initialArticle =
        Article("first Article", "text of first article. blablalbalb. blalbla. bla.")

    @Before
    fun setup() {
        viewModel = MainViewModel(initialArticle)
    }

    @Test
    fun testArticleEmission() {
        Assert.assertEquals(initialArticle, viewModel.articleLiveData.value)
    }

    @Test
    fun testArticleTitleEmission() {
        Assert.assertEquals(initialArticle.title, viewModel.articleTitleLiveData.value)
    }
}

But it might surprise you that the 2nd test testArticleTitleEmission will fail. Why is that?

Turns out that transformed LiveData does not emit values without an observer. So to make the test pass you need to adjust it in the following way:

@Test
fun testArticleTitleEmission() {
    viewModel.articleTitleLiveData.observeForever { } // This is the important bit!!
    Assert.assertEquals(initialArticle.title, viewModel.articleTitleLiveData.value)
}

Note: It doesn’t matter what your observer does, just that it exists.

Now both tests will pass.

#4 Testing: The disadvantage of static methods

What are static methods?

  • Static methods belong to the class rather than an instance of the class.
  • Static methods are not inherited and thus cannot be overridden.
  • You cannot use static methods for declaring a contract via an interface.

These facts are in themselves not bad.

Why are static methods bad for testing? 

Let’s say we want to test the following createCommand method using JUnit Jupiter:

public class Mapper {
    public Command createCommand(final Event event) {
        return Command.builder()
                .commandUuid(UUID.randomUUID().toString())
                .id(event.getEventId())
                .name(event.getName()).build();
    }
}

@Builder
@Getter
@EqualsAndHashCode
public class Event {
    private String type;
    private String eventId;
    private String name;
}

@Builder
@Getter
@EqualsAndHashCode
public class Command {
    private String commandUuid;
    private String name;
    private String id;
}

But mocking the static UUID.randomUUID() method is not possible with most testing frameworks. This is where the problems starts:

We don’t know what to put as commandUuid because it get’s generated inside the mapping by using a static method. This dependency is hidden for us.

public class TestMapping {

    @Test
    public void testMapping() {
        Mapper mapper = new Mapper();
        String eventId = UUID.randomUUID().toString();
        String name = "name";
        Event event = Event.builder()
                .eventId(eventId)
                .name(name)
                .type("create")
                .build();
        Command command = mapper.createCommand(event);

        Command expected = Command.builder()
                .name(name)
                .id(eventId)
                .commandUuid("Don't know what to do here? ?")
                .build();

        Assertions.assertEquals(command, expected);
    }
}

So what could do we do instead?

  1. add dependencies for Mockito and Mockito JUnit Jupiter support
  2. Create UuidGenerator class with a generateUuid() method (if you want you could even go with an interface, but let’s keep it simple for now)
  3. inject UuidGenerator instance to the Mapper class.
  4. Mock the UuidGenerators generateUuid method to always return the same uuid.

Our code will now look like this:

public class UuidGenerator{
    public String generateUuid(){
       return UUID.randomUUID().toString()
    }
}

public class Mapper {

    private final UuidGenerator uuidGenerator;

    public Mapper(UuidGenerator uuidGenerator) {
        this.uuidGenerator = uuidGenerator;
    }

    public Command createCommand(final Event event) {
        return Command.builder()
                .commandUuid(uuidGenerator.generateUuid())
                .id(event.getEventId())
                .name(event.getName()).build();
    }
}


@ExtendWith(MockitoExtension.class)
public class TestMapping {

    private Mapper mapper;

    @Mock
    private UuidGenerator uuidGenerator;

    @BeforeEach
    public void before() {
        mapper = new Mapper(uuidGenerator);
        String commandId = UUID.randomUUID().toString();
        Mockito.when(uuidGenerator.generateUuid()).thenReturn(commandId);
    }

    @Test
    public void testMapping() {
        String eventId = UUID.randomUUID().toString();
        String name = "name";
        Event event = Event.builder()
                .eventId(eventId)
                .name(name)
                .type("create")
                .build();
        Command command = mapper.createCommand(event);

        Command expected = Command.builder()
                .name(name)
                .id(eventId)
                .commandUuid(uuidGenerator.generateUuid())
                .build();

        Assertions.assertEquals(command, expected);
    }
}

One could argue that we could write a custom equalsData() function or something similar. Or check for all fields separately? But what if we’ll change one of the Event or Command classes? We would always need to think about adjusting these methods and imagine bigger data classes with lots of fields: everything would get very cluttered.

Apart from that this is just an example, this principle is applicable to many more cases.