views:

357

answers:

5

Hi All,

This is a general question on how to unit-test a Java Class using mock objects.
I can summarize my problem with this example.
Let's say I've an Interface called MyInterface.java and a "TwoString" Object that doesn't override equals()

"TwoString.java"

   private String string1;
   private String string2;

   public TwoString(String string1, String string2) {
     this.string1 = string1;
     this.string2 = string2;
   }
   ...getters..setters..

"MyInterface.java"

void callMe(TwoString twoString);

Then I have "MyClass.java" Object. Its constructor accepts a concrete implementation of MyInterface.
MyClass methodToTest() contains the logic to create a TwoString oject in some way. Let's say that it will be created as

new TwoString("a","b")

So when methodToTest() is called it creates this TwoString object that will be passed to the Interface method callMe(TwoString twoString).

I basically want to mock the Interface. Create a MyClass object with this mock. Then verify that the mock method is called with a specific instance of TwoString.

I'm using EasyMock and this is some java code

"MyClassTest.java"

public void test() throws Exception {   
   MyInterface myInterfaceMock = createMock(MyInterface.class);
   MyClass myClass = new MyClass(myInterfaceMock);

   myInterfaceMock.callMe(new TwoString("a","b"));   <--- fails here
   expectLastCall();
   replay(myInterfaceMock);

   myClass.methodToTest();
   verify(myInterfaceMock);

Here comes the problem. The TwoString object that I'm expecting in the call

myInterfaceMock.callMe(new TwoString("a","b"));

is different from the one generated in MyClass.methodToTest() because TwoString.java doesn't override equals.

I can skip the problem on the TwoString instance using

myInterfaceMock.callMe((TwoString)anyObject());

but I want to be sure that the interface method is called with a specific instance of TwoString that contains "a" as string1 and "b" as string2.

In this case the TwoString object is very simple and it will be easy to override the equals method - but what if I need to check a more complex object.

Thanks

edit:

I'll try to make it more readable with this example

public class MyClassTest {
    private MyClass myClass;
    private TaskExecutor taskExecutorMock;

    @Before
    public void setUp() throws Exception {
     taskExecutorMock = createMock(TaskExecutor.class);
     myClass = new MyClass(taskExecutorMock);
    }

    @Test
    public void testRun() throws Exception {
     List<MyObj> myObjList = new ArrayList<MyObj>();
     myObjList.add(new MyObj("abc", referenceToSomethingElse));

     taskExecutorMock.execute(new SomeTask(referenceToSomethingElse,  ???new SomeObj("abc", referenceToSomethingElse, "whatever")));   <--- ??? = object created using data in myObjList
     expectLastCall();
     replay(taskExecutorMock);

     myClass.run(myObjList);

     verify(taskExecutorMock);
    }
}

???SomeObj = object created by myClass.run() using data contained in myObjList.
Let's say that SomeObj comes from a third party library and it doesn't override equals.

I want to be sure that the taskExecutorMock.execute() method is getting called with a specific instance of that SomeObj

How can I test that the myClass.run() is actually calling the taskExecutorMock method with a correct instance

+2  A: 

First up - you probably mean "override equals" rather than implement, since all classes have some implementation of equals (the one they inherit from Object if they don't override anything else).

The answer in this case is simple - all value objects really really ought to implements equals and hashcode. Whether it's a simple one like TwoString, or the more complex object you allude to, it should be the object's responsibility to check whether it is equal to some other object.

The only other alternative would be to basically deconstruct the object in your test code - so instead of

assertEquals(expected, twoStr);

you'd do

assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());

Hopefully you can see that this is bad in at least three ways. Firstly, you're basically duplicating the logic that should be in the class' own equals() method; and anywhere that you want to compare these objects you'll have to write the same code.

Secondly, you can only see the object's public state, there could well be some private state that causes two apparently similar objects to be not equal (e.g. a Lift class could have a publically accessible "floor" attribute, but private "going up or down" state too).

Finally, it's a violation of the Law of Demeter for a third-party class to be basically messing about with the internals of TwoString trying to work out whether the things are equal.

The object itself should implement its own equals() method - pure and simple.

Andrzej Doyle
Yes I mean override.I don't like the idea of having to override equals() only for test purposes. I'll also have to override it for the nested objects if the object I'm using is more complex. I was wondering if there is another way to achieve this.
al nik
It's not *just* for test purposes - if your class is conceptually comparable to other instances of itself (i.e. if it's a value class) then it really should override equals() (**and hashcode()**, the two must always be considered together) in order to behave properly. It always has an equals() method anyway, don't forget. Why leave it with the default (wrong for this class) implementation?
Andrzej Doyle
having equals in all my java objects seems to much for me. I'll have to override it only for test purposes because I don't actually need it in my code! My real TwoString is more complex than a couple of Strings values and it contains other objects that at the same time need to override equals even if I don't need it outside the tests. In every project I saw so far few specific classes were overriding equals. If I don't need it I don't see the problem of having the default implementation.
al nik
As your question shows - you **do** need it!
Andrzej Doyle
Besides, whether you like it or not, your class has an `equals()` method, which other code will call and expect it to behave appropriately. This includes library code, in particular basically every standard Java collection. Not implementing equals() properly will quite likely lead to strange behaviour if you ever put your class in a collection, and will manifest in difficult to track down behaviour. Why are you so resistant to adding appropriate functionality to your class?
Andrzej Doyle
simply because that class can come from a third party library
al nik
If a value class doesn't implement equals() properly, then it's broken. In which case I'd suggest submitting a bug request + patch to the maintainer, but in the meantime write a static method (in your "commons" package/module) to compare equality of these two objects as best you can via their public interface.
Andrzej Doyle
I needed a custom way of matching an object. It can contain anything. I don't really understand why should I introduce equals in all the classes I have in my proj. How do you check equals lets say on a IllegalStateException object? If a class doesn't override equals doesn't mean it's buggy/broken.Anyway thanks a lot for your response ;) I think that org.easymock.IArgumentMatcher as suggested by waxwing can really solve the problem
al nik
OK, I think I see what you mean now - it's seeing whether certain aspects of two objects are equal. In which case I agree, the IArgumentMatcher is the right way to go about it. In other news, this it the longest comment chain I've had in a while; thanks for being responsive. :-)
Andrzej Doyle
A: 

You can achieve this with argument captors in Mockito 1.8.

http://mockito.googlecode.com/svn/branches/1.8.0/javadoc/org/mockito/Mockito.html#15

I know you are using EasyMock but changing to Mockito is easy and it's much better!

Pablojim
I'm tempted to -1 this, because: a.) you don't explain *how* it's done using Mockito and b.) you claim that Mockito is much better but don't say why or how it is better. Pure FUD/marketing in my opinion.
Joachim Sauer
+2  A: 

Take a look at Jakarta Commons Lang: EqualsBuilder.reflectionEquals()

While I agree with dtsazza that all value objects should have an equals() (and hashCode()) method, they're not always appropriate to testing: most value objects will base equality on a key, rather than on all fields.

At the same time, I'm leery of any test that wants to check all fields: it seems to me to be saying "make sure this method didn't change something that I wasn't planning for it to change." Which is a valid test, and on some level a very well-meaning test, but it's a little scary that you feel the need for it.

kdgregory
If the object bases equality on a key, write an `equals()` method that just tests that key. :-) Really, a tenet of Java is that all objects can be compared for equality with other objects, so equals() should be implemented appropriate for any class.
Andrzej Doyle
That comment indicates that I wasn't being clear in my post. I agree that value objects should have equals(). However, an implementation that is appropriate for the use of a value object (key based) may not be appropriate for testing changes to non-key fields in that object.
kdgregory
A: 

In this case the TwoString object is very simple and it will be easy to override the equals method - but what if I need to check a more complex object.

Once your objects start becoming so complex that you can't trivially check if they're equal from elsewhere, you should probably refactor and inject them as a dependency. This would change the design, but usually that's for the better.

You also seem to be relying on some knowledge of the internal behaviour of your classes. The above is an interaction test between two classes, which still sort of works, but the bigger your set of tested components gets, the less you can really still talk about "unit" tests. At a certain point you leave the realm of unit tests and you start doing integration tests, in which case you might be better off with a full blown test harness and isolating behaviour in certain places...

wds
Thanks for your response. I edited my question with another example.I'm not interacting with any other class. I think that is a unit-test rather then an integration test. Then only collaborator I have is a mock basically.
al nik
+4  A: 

It is possible to use a custom equals matching method using org.easymock.IArgumentMatcher.

It should look something like:

private static <T extends TwoString> T eqTwoString(final TwoString twoString) {
    reportMatcher(new IArgumentMatcher() {
        /** Required to get nice output */
        public void appendTo(StringBuffer buffer) {
            buffer.append("eqTwoString(" + twoString.getString1() + "," + twoString.getString2() + ")");
        }

        /** Implement equals basically */
        public boolean matches(Object object) {
            if (object instanceof TwoString) {
                TwoString other = (TwoString) object;
                // Consider adding null checks below
                return twoString.getString1().equals(object.getString1()) && twoString.getString2().equals(object.getString2());
            }
            else {
                return false;
            }
        }
    });
    return null;
}

And is used as follows:

myInterfaceMock.callMe(eqTwoString(new TwoString("a","b")));

Some details may not be correct, but in essence it's how I've done it before. There is another example and more thorough explanations available in the EasyMock documentation. Just search for IArgumentMatcher.

waxwing
Thanks a lot waxwing! I'll try this solution - it definitely seems perfect for what I need to achieve! Thanks again
al nik
By the way, the reportMatcher method is available in org.easymock.EasyMock, I just happened to have it statically imported.
waxwing