s-hunter
s-hunter

Reputation: 25816

How to unit test a list of objects using TestObserver in RxJava 2?

Here is a Person class for holding person object. It has Comparable implementation for comparison.

public class Person implements Comparable<Person> {

    private String firstName = "";
    private String lastName = "";

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public int compareTo(@NonNull Person person) {
        return this.firstName.compareTo(person.getFirstName());
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Here is a factory class for creating the people list and people list Observables. It always create the same set of people.

import java.util.ArrayList;
import io.reactivex.Observable;

public class PeopleFactory {

    public static ArrayList<Person> createPeople() {
        ArrayList<Person> people = new ArrayList<>();

        for (int i=0; i<10; i++) {
            people.add(new Person("Thanks" + i, "Giving" + i));
        }

        return people;
    }

    public static Observable<ArrayList<Person>> createPersonObservable() {
        return Observable.just(createPeople());
    }
}

Now I want use the TestObserver in RxJava 2 to test the people list against the people list from the people emitted from the people Observable.

import org.junit.Test;
import java.util.ArrayList;
import io.reactivex.Observable;
import io.reactivex.observers.TestObserver;


public class PersonTest {
    @Test
    public void testPeople() {
        // Creating a people Observable
        Observable<ArrayList<Person>> peopleObservable = PeopleFactory.createPersonObservable();

        // Creating an test observer and subscribe to the above peopleObservable
        TestObserver<ArrayList<Person>> observer = new TestObserver<>();
        peopleObservable.subscribe(observer);

        // Creating the same people list and assert this people list with the people list from the peopleObservable
        ArrayList<Person> people = PeopleFactory.createPeople();
        observer.assertValue(people);
    }
}

I am expecting this test should pass because the PeopleFactory is creating the same set of people for a normal people list and a people list Observable, and the Person class has a Comparable which says the object will be equal as long as the firstName are equal.

But the test ignored this Comparable, compared it against the object addresses, and gave me the following test failure message:

java.lang.AssertionError: Expected: [com.example.myapplication.Person@7006c658, com.example.myapplication.Person@34033bd0, com.example.myapplication.Person@47fd17e3, com.example.myapplication.Person@7cdbc5d3, com.example.myapplication.Person@3aa9e816, com.example.myapplication.Person@17d99928, com.example.myapplication.Person@3834d63f, com.example.myapplication.Person@1ae369b7, com.example.myapplication.Person@6fffcba5, com.example.myapplication.Person@34340fab] (class: ArrayList), Actual: [com.example.myapplication.Person@2aafb23c, com.example.myapplication.Person@2b80d80f, com.example.myapplication.Person@3ab39c39, com.example.myapplication.Person@2eee9593, com.example.myapplication.Person@7907ec20, com.example.myapplication.Person@546a03af, com.example.myapplication.Person@721e0f4f, com.example.myapplication.Person@28864e92, com.example.myapplication.Person@6ea6d14e, com.example.myapplication.Person@6ad5c04e] (class: ArrayList) (latch = 0, values = 1, errors = 0, completions = 1)

    at io.reactivex.observers.BaseTestConsumer.fail(BaseTestConsumer.java:163)
    at io.reactivex.observers.BaseTestConsumer.assertValue(BaseTestConsumer.java:332)
    at com.example.myapplication.PersonTest.testPeople(PersonTest.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

So, what is the correct way or how can I unit test a list of objects using TestObserver in RxJava 2?

Note: All of the above code were written in Android Studio.

Upvotes: 2

Views: 2462

Answers (2)

s-hunter
s-hunter

Reputation: 25816

Thanks to @Murka's answer, it works as expected after I replaced the Comparable implementation with @Override public boolean equals(Object obj)

public class Person {

    private String firstName = "";
    private String lastName = "";

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }


    @Override
    public boolean equals(Object obj) {
        return this.firstName.equalsIgnoreCase(((Person) obj).getFirstName());
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Upvotes: 1

Murka
Murka

Reputation: 361

From the javadoc of Comparable:

This interface imposes a total ordering on the objects of each class that implements it. ... It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)).

The Comparable::compareTo method is only used when sorting or otherwise comparing the order of elements. Equality is entirely separate and as you noted, the object identities are being asserted.

You can quickly confirm what the framework is doing by looking at the source https://github.com/ReactiveX/RxJava/blob/v2.1.6/src/main/java/io/reactivex/observers/BaseTestConsumer.java#L331

if (!ObjectHelper.equals(value, v)) {
  throw fail("Expected: " + valueAndClass(value) + ", Actual: " + valueAndClass(v));
}

ObjectHelper::equals

public static boolean equals(Object o1, Object o2) { // NOPMD
  return o1 == o2 || (o1 != null && o1.equals(o2));
}

So it's calling Object::equals which you have not overloaded for the Person class so the test uses object identity and fails as expected.

Upvotes: 3

Related Questions