Reputation: 25816
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
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
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