GreenSaguaro
GreenSaguaro

Reputation: 3387

EasyMock: How to Verify Method Order for Set of Values Where Order of Set Does Not Matter

I have a test in which I have a set of specific values for which two different methods will execute once for each value in the set. I need to check that the two methods are called in a specific order in relation to each other, but not in relation to the order of the set of values. For example:

String[] values = { "A", "B", "C" };

for (...<loop over values...) {
    methodOne(value);
    methodTwo(value);
}

It does not matter which order values is in, but I need to verify that methodOne() and methodTwo() are called for each value in the set AND that methodOne() is always called before methodTwo().

I know that I can create a control and expect methodOne() and methodTwo() for each value, then do control.verify(), but this depends on values being in a specific order.

Is there an elegant way to do this?

Thanks

Upvotes: 3

Views: 1215

Answers (2)

GreenSaguaro
GreenSaguaro

Reputation: 3387

For those interested, I solved this issue using intended EasyMock functionality. The solution was to make a custom IArgumentMatcher to verify against a collection of values and to enforce how many times each value is matched consecutively. The custom matcher, in addition to using strict mocking exactly solves the original problem.

public class SetMatcher implements IArgumentMatcher {

    private List<String> valuesToMatch;
    private List<String> remainingValues;
    private String currentValue = null;
    private int timesMatched = 0;
    private int setMatches;

    public SetMatcher(final List<String> valuesToMatch, final int times) {
        this.valuesToMatch = new ArrayList<String>(valuesToMatch);
        this.remainingValues = new ArrayList<String>(valuesToMatch);
        this.setMatches = times;
    }

    public String use() {
        EasyMock.reportMatcher(this);

        return null;
    }

    public void appendTo(StringBuffer buffer) {
        if (this.remainingValues.size() == 0) {
            buffer.append("all values in " + this.valuesToMatch + " already matched " + this.setMatches + " time(s)");
        } else {
            buffer.append("match " + this.valuesToMatch + " " + this.setMatches + " time(s) each");
        }
    }

    public boolean matches(Object other) {

        if (this.timesMatched >= this.setMatches) {
            this.currentValue = null;
            this.timesMatched = 0;
        }

        if (null == this.currentValue) {
            if (this.remainingValues.contains(other)) {
                this.currentValue = (String) other;
                this.timesMatched = 1;
                this.remainingValues.remove(other);

                return true;
            }
        } else if (this.currentValue.equals(other)) {
            this.timesMatched++;

            return true;
        }

        return false;
    }

}

The class being tested:

public class DataProcessor {
    private ServiceOne serviceOne;
    private ServiceTwo serviceTwo;

    public DataProcessor(ServiceOne serviceOne, ServiceTwo serviceTwo) {
        this.serviceOne = serviceOne;
        this.serviceTwo = serviceTwo;
    }

    public void processAll(List<String> allValues) {
        List<String> copy = new ArrayList<String>(allValues);
        for (String value : copy) {
            this.serviceOne.preProcessData(value);
            this.serviceTwo.completeTransaction(value);
        }
    }
}

And the test:

public class DataProcessorTest {

    List<String> TEST_VALUES = Arrays.asList("One", "Two", "Three", "Four", "Five");

    @Test
    public void test() {
        IMocksControl control = EasyMock.createStrictControl();
        ServiceOne serviceOne = control.createMock(ServiceOne.class);
        ServiceTwo serviceTwo = control.createMock(ServiceTwo.class);

        SetMatcher matcher = new SetMatcher(TEST_VALUES, 2);

        for (int i = 0; i < TEST_VALUES.size(); i++) {
            serviceOne.preProcessData(matcher.use());
            serviceTwo.completeTransaction(matcher.use());
        }

        control.replay();

        DataProcessor dataProcessor = new DataProcessor(serviceOne, serviceTwo);
        dataProcessor.processAll(TEST_VALUES);

        control.verify();
    }
}

The test will fail for any of the following:

  • ServiceOne and ServiceTwo are called in the wrong order
  • ServiceOne and ServiceTwo are not called consecutively with the same value
  • ServiceOne or ServiceTwo are called with a value that is not in the specified value list
  • A call is made beyond the number of expected times for a value in the list

Upvotes: 0

dkatzel
dkatzel

Reputation: 31648

You can do this using andAnswer().

Basically, inside the andAnswer() from methodOne() you set some variable to hold what the passed in value was.

Then in the andAnswer() for methodTwo() you assert that the same argument matches what you saved from your methodOne answer.

Since each call to methodOne will modify this variable it will make sure methodTwo() is always called after methodOne().

Note this solution is not thread safe

First you need something to hold the variable from the methodOne call. This can be a simple class with a single field or even an array of one element. You need this wrapper object because you need to reference it in the IAnswer which requires a final or effectively final field.

private class CurrentValue{
    private String methodOneArg;

}

Now your expectations. Here I called the class that you are testing (The System Under Test) sut:

    String[] values = new String[]{"A", "B", "C"};

    final CurrentValue currentValue = new CurrentValue();

    sut.methodOne(isA(String.class));

    expectLastCall().andAnswer(new IAnswer<Void>() {

        @Override
        public Void answer() throws Throwable {
            //save the parameter passed in to our holder object
            currentValue.methodOneArg =(String) EasyMock.getCurrentArguments()[0];
            return null;
        }

    }).times(values.length); // do this once for every element in values

    sut.methodTwo(isA(String.class));
    expectLastCall().andAnswer(new IAnswer<Void>() {

        @Override
        public Void answer() throws Throwable {
            String value =(String) EasyMock.getCurrentArguments()[0];
            //check to make sure the parameter matches the 
            //the most recent call to methodOne()

            assertEquals(currentValue.methodOneArg, value);
            return null;
        }

    }).times(values.length); // do this once for every element in values

    replay(sut);
    ... //do your test
    verify(sut);

EDIT

you are correct that if you are using EasyMock 2.4 + you can use the new Capture class to get the argument value in a cleaner way for methodOne(). However, you may still need to use the andAnswer() for methodTwo() to make sure the correct values are called in order.

Here is the same code using Capture

Capture<String> captureArg = new Capture<>();

    sut.methodOne(and(capture(captureArg), isA(String.class)));
    expectLastCall().times(values.length);

    sut.methodTwo(isA(String.class));
    expectLastCall().andAnswer(new IAnswer<Void>() {

        @Override
        public Void answer() throws Throwable {
            String value =(String) EasyMock.getCurrentArguments()[0];
            assertEquals(captureArg.getValue(), value);
            return null;
        }

    }).times(values.length);

    replay(sut);

Upvotes: 1

Related Questions