An interesting gotcha in Java unittesting

Those of you who have more than a passing familiarity with Java will be aware of Java's semantics for object equality using '==': it returns true iff the two arguments are the same object. There's no operator overloading for the equals operator, even by built in classes; classes that want to do value equality tests are expected to override the built in 'equals()' method.

This leads to a common pitfall with Strings. The following compiles fine, with no warnings:

public static void isFoo(String s) {
  return s == "foo";
}

This won't work as intended, of course, because it will only return true if the passed in string is the very same object as our string literal "foo". Let's write a unittest to make sure everything works as expected:

@Test
public void testIsFoo() {
  assertTrue(Blah.isFoo("foo"));
  assertFalse(Blah.isFoo("bar"));
}

But wait! This test passes just fine! Something is very wrong here.

What's happening is that the Java compiler is interning string literals, so that multiple uses of the same string constant point to the same actual string object at runtime. This saves memory, but it's making it tough for us to test properly. You might think you can fix the test like this:

@Test
public void testIsFoo() {
  assertTrue(Blah.isFoo("fo" + "o"));
  assertFalse(Blah.isFoo("bar"));
}

Unfortunately, this test passes too - the Java compiler is smart enough to evaluate the string concatenation at compile time, and again interns the literals to point at the same string.

The best way to make sure you're testing with distinct strings is to construct a new string from your string literal, thus ensuring it has to be an entirely new object, like this:

@Test
public void testIsFoo() {
  assertTrue(Blah.isFoo(new String("foo")));
  assertFalse(Blah.isFoo(new String("bar")));
}

Solved! I've always said you have to be careful around interns.

Comments

blog comments powered by Disqus