Search code examples
javaunit-testingcollectionsfloating-pointfloating-point-comparison

Java List.contains object with double with tolerance


Let's say I have this class:

public class Student
{
  long studentId;
  String name;
  double gpa;

  // Assume constructor here...
}

And I have a test something like:

List<Student> students = getStudents();
Student expectedStudent = new Student(1234, "Peter Smith", 3.89)
Assert(students.contains(expectedStudent)

Now, if the getStudents() method calculates Peter's GPA as something like 3.8899999999994, then this test will fail because 3.8899999999994 != 3.89.

I know that I can do an assertion with a tolerance for an individual double/float value, but is there an easy way to make this work with "contains", so that I don't have to compare each field of Student individually (I will be writing many similar tests, and the actual class I will be testing will contain many more fields).

I also need to avoid modifying the class in question (i.e. Student) to add custom equality logic.

Also, in my actual class, there will be nested lists of other double values that need to be tested with a tolerance, which will complicate the assertion logic even more if I have to assert each field individually.

Ideally, I'd like to say "Tell me if this list contains this student, and for any float/double fields, do the comparison with tolerance of .0001"

Any suggestions to keep these assertions simple are appreciated.


Solution

  • The behavior of List.contains() is defined in terms of the equals() methods of the elements. Therefore, if your Student.equals() method compares gpas for exact equality and you cannot change it then List.contains() is not a viable method for your purpose.

    And probably Student.equals() shouldn't use a comparison with tolerance, because it's very hard to see how you could make that class's hashCode() method consistent with such an equals() method.

    Perhaps what you can do is write an alternative, equals-like method, say "matches()", that contains your fuzzy-comparison logic. You could then test a list for a student fitting your criteria with something like

    Assert(students.stream().anyMatch(s -> expectedStudent.matches(s)));
    

    There is an implicit iteration in that, but the same is true of List.contains().