views:

995

answers:

5

This might seem like a pretty detailed question about Easymock, but I'm having a hard time finding a support site/forum/mailing list for this library.

I'm encountering a bug when using the captures() method that seems to return the captured parameters out of order.

Here's a simplified version of what I am testing:

public class CaptureTest extends TestCase {

    // interface we will be mocking
    interface Processor {
            void process(String x);
    }

    // class that uses the interface above which will receive the mock
    class Component {

        private Processor processor;

        private String[] s = { "one", "two", "three", "four" };

        Component(Processor processor) {
            this.processor = processor;
        }

        public void doSomething() {
            for (int i = 0; i < s.length; i++) {
                processor.process(s[i]);
            }
        }
}

    public void testCapture() {

        //create the mock, wire it up    
        Processor mockProcessor = createMock(Processor.class);
        Component component = new Component(mockProcessor);

        //we're going to call the process method four times
        //with different arguments, and we want to capture 
        //the value passed to the mock so we can assert against it later    
        Capture<String> cap1 = new Capture<String>();
        Capture<String> cap2 = new Capture<String>();
        Capture<String> cap3 = new Capture<String>();
        Capture<String> cap4 = new Capture<String>();

        mockProcessor.process(and(isA(String.class), capture(cap1)));
        mockProcessor.process(and(isA(String.class), capture(cap2)));
        mockProcessor.process(and(isA(String.class), capture(cap3)));
        mockProcessor.process(and(isA(String.class), capture(cap4)));

        replay(mockProcessor);

        component.doSomething();

        //check what values were passed to the mock
        assertEquals("one", cap1.getValue());
        assertEquals("two", cap2.getValue());
        assertEquals("three", cap3.getValue());
        assertEquals("four", cap4.getValue());

        verify(mockProcessor);
    }

}

(Please note that this is just a simplified test case - I know that I could specify the exact value of the arguments I expect passed to my mock, but in my real case the arguments are complex objects with a handful of fields, and I want to capture the object so I can assert against just a few of those fields without re-creating the entire object in my test case).

When I run the test, it fails at:

junit.framework.ComparisonFailure: expected:<[one]> but was:<[four]>

Meaning that the parameter that EasyMock is capturing in cap1 is not the first call to the method, but the last (since the value is four). I get the same results if I reverse the captures() declarations, i.e. use cap4 with the first method call, etc.

This seems like it might be a bug within EasyMock - different parameters passed to the same method in different invocations don't seem to be capture correctly.

Is anyone else using capture() with EasyMock and having similar problems? Is there an easy workaround you know of, or a different way I can capture the parameters being passed to my mock's methods?

Update 1: fixed code sample to show I am using createMock, not createStrictMock, but I get the same error with both (although the actual value of what is captured changes).

A: 

Instead of calling EasyMock.createStrictMock(...) just call EasyMock.createMock(...). Should solve your problems.

Il-Bhima
I had the same problems with createMock - I changed it to createStrickMock (and forgot it was in the code sample I posted) thinking that might help with the order of the method calls, but nope - same result. I'll update the sample above.
matt b
Hmm, just as a check, could you try calling checkOrder(false) after you create the mock. I might be missing something obvious but its looking like a bug. I'll be able to confirm it on my machine tonight.
Il-Bhima
same results with checkOrder(false)
matt b
A: 

You can also try using EasyMock.createNiceMock(...) instead of EasyMock.createStrictMock(...) or EasyMock.createMock(...).

Although, I agree that it looks more like a bug with createMock.

Jared
+1  A: 

Hi hooknc, I was playing around with your test and could not solve. However I extended the Capture Class to see if the values were set in a different order (I was suspicious that EasyMock internally was using a hash with a key generated from the methods and the parameters) I was wrong the methods are set in the correct order. But there is something really weird going on.. It seems that the algorithm does some kind assigning pattern.. Well let me show the code and the strange output.... BTW the changes from mock, niceMock and strictMock didn't make anydifference..

class MyCapture extends Capture<String> {

 private String id;

 public MyCapture(String id) {
  super();
  System.out.printf("Constructor %s expecting %s\n", id, this.getClass().getName());
  this.id = id;
 }

 private static final long serialVersionUID = 1540983654657997692L;

 @Override
 public void setValue(String value) {
  System.out.printf("setting value %s expecting %s \n", value, id);
  super.setValue(value);
 }

 @Override
 public String getValue() {
  System.out
    .printf("getting value %s expecting %s \n", super.getValue(), id);
  return super.getValue();
 }
}


public void testCapture() {

 // create the mock, wire it up
 Processor mockProcessor = createStrictMock(Processor.class);
 Component component = new Component(mockProcessor);

 // we're going to call the process method four times
 // with different arguments, and we want to capture
 // the value passed to the mock so we can assert against it later
 Capture<String> cap1 = new MyCapture("A");
 Capture<String> cap2 = new MyCapture("B");
 Capture<String> cap3 = new MyCapture("C");
 Capture<String> cap4 = new MyCapture("D");

 mockProcessor.process(and(isA(String.class), capture(cap1)));
 mockProcessor.process(and(isA(String.class), capture(cap2)));
 mockProcessor.process(and(isA(String.class), capture(cap3)));
 mockProcessor.process(and(isA(String.class), capture(cap4)));
 replay(mockProcessor);

 component.doSomething();

 // check what values were passed to the mock
 assertEquals("A", cap1.getValue());
 assertEquals("B", cap2.getValue());
 assertEquals("C", cap3.getValue());
 assertEquals("D", cap4.getValue());

 verify(mockProcessor);
}

}

*And this is the output *

Constructor A expecting com.comp.core.dao.impl.CaptureTest$MyCapture
    Constructor B expecting com.comp.core.dao.impl.CaptureTest$MyCapture
    Constructor C expecting com.comp.core.dao.impl.CaptureTest$MyCapture
    Constructor D expecting com.comp.core.dao.impl.CaptureTest$MyCapture
    calling process A 
    setting value A expecting A 
    calling process B 
    setting value B expecting A <<Setting the wrong guy
    setting value B expecting A <<Setting the wrong guy
    setting value B expecting B <<Ops this is the right one..stop
    calling process C 
    setting value C expecting B <<Setting the wrong guy
    setting value C expecting B <<Setting the wrong guy
    setting value C expecting C <<Setting the wrong guy
    calling process D 
    setting value D expecting C <<Setting the wrong guy
    setting value D expecting C <<Setting the wrong guy
    setting value D expecting D <<Ops this is the right one..stop
    getting value B expecting A

Sorry I can't help you more. It might be indeed a bug in easy mock.

pribeiro
+2  A: 

I've received an answer on the bug I submitted to the Easymock sourceforge site, and a developer has confirmed it is indeed a bug with this version of Easymock.

It is indeed a bug. The capture is done even if it was already done. The current workaround is to implement your own capture object and override setValue to do this:

@Override
public void setValue(T value) {
  if(!hasCaptured()) {
    super.setValue(value);
  }
}
matt b
A: 

This is a problem more appropriate for state-based testing, I think. With JMockit, you could solve it like this:

import mockit.*;
import static mockit.Mockit.*;
import mockit.integration.junit3.*;

public class CaptureTest extends JMockitTestCase
{
   interface Processor { void process(String x); }

   class Component
   {
      private final Processor processor;
      private final String[] s = {"one", "two", "three", "four"};

      Component(Processor processor) { this.processor = processor; }

      public void doSomething()
      {
         for (String value : s) {
            processor.process(value);
         }
      }
   }

   @MockClass(realClass = Processor.class)
   static class MockProcessor
   {
      private final String[] expectedValues;
      private int i;

      MockProcessor(String... expectedValues) { this.expectedValues = expectedValues; }

      @Mock
      void process(String x)
      {
         assertEquals(expectedValues[i++], x);
      }
   }

   public void testCapture()
   {
      Processor mockProcessor = setUpMock(new MockProcessor("one", "two", "three", "four"));
      Component component = new Component(mockProcessor);

      component.doSomething();
   }
}
Rogerio