JUnit Easymock - spurious results invoking unit test method as a) method b) within class c) as mvn build


JUnit Easymock - spurious results invoking unit test method as a) method b) within class c) as mvn build



I have a unit test and contained within this unit test is my problem method:


@Test(expected = CheckoutException.class)
public void performCheckout_CheckoutException() throws Exception {

// setup test data
Order order = new OrderImpl();
OMSOrder omsOrder = new OMSOrderImpl();
Order omsOrderProxy = OMSOrderProxy.proxify(order, omsOrder, Logger.getRootLogger());
omsOrderProxy.setId(1L);

FulfillmentOrder fulfillmentOrder = new FulfillmentOrderImpl();
FulfillmentGroup fulfillmentGroup = new FulfillmentGroupImpl();
fulfillmentGroup.setType(FulfillmentType.DIGITAL);
fulfillmentOrder.setFulfillmentGroup(fulfillmentGroup);

((OMSOrder)omsOrderProxy).getAllFulfillmentOrders().add(fulfillmentOrder);

ProcessContext<CheckoutSeed> context = new DefaultProcessContextImpl<>();

// create the expected flow
expect(orderService.save(anyObject(Order.class), eq(false))).andReturn(order).times(2);
replay(orderService);

expect((ProcessContext<CheckoutSeed>)checkoutWorkflow.doActivities(anyObject(CheckoutSeed.class))).andReturn(context);
replay(checkoutWorkflow);

expect(fulfillmentService.fulfill(anyObject(FulfillmentOrder.class))).andThrow(new FulfillmentException());
replay(fulfillmentService);

// test
checkoutService.performCheckout(omsOrderProxy);

// check results
verify(orderService);
verify(checkoutWorkflow);
verify(fulfillmentService);

}



orderService is a strict mock (defined in a @Before setup method):


orderService


@Before



orderService = createStrictMock(OrderService.class);


orderService = createStrictMock(OrderService.class);



Each and every unit test class that uses this orderService creates this mock (whether strict or nice) in this @Before setup method.


orderService


@Before



Running this test method in Intellij (right-click, Run ...) achieves a successful result. Running the test at class level, again right-click, Run ... achieves another successful result. A mvn clean install (whether in Intellij or at the command line) renders the following error:


java.lang.Exception: Unexpected exception, expected<org.curtiscommerce.core.checkout.service.exception.CheckoutException> but was<java.lang.IllegalStateException>
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:52)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:41)
at org.easymock.internal.RecordState.invoke(RecordState.java:51)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:40)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:94)
at com.sun.proxy.$Proxy27.save(Unknown Source)
at com.central.core.checkout.service.TestCheckoutServiceImpl.performCheckout_CheckoutException(TestCheckoutServiceImpl.java:151)



Line 151 (detailed in the code line directly above) relates to:


expect(orderService.save(anyObject(Order.class), eq(false))).andReturn(order).times(2);



which is a line in this method.



Now to get the exception details I remove the 'expected' attribute from the @Test annotation and the exception thrown is clearer:


@Test


java.lang.IllegalStateException: 2 matchers expected, 12 recorded.
This exception usually occurs when matchers are mixed with raw values when recording a method:
foo(5, eq(6)); // wrong
You need to use no matcher at all or a matcher for every single param:
foo(eq(5), eq(6)); // right
foo(5, 6); // also right
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:52)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:41)
at org.easymock.internal.RecordState.invoke(RecordState.java:51)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:40)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:94)
at com.sun.proxy.$Proxy27.save(Unknown Source)
at com.central.core.checkout.service.TestCheckoutServiceImpl.performCheckout_CheckoutException(TestCheckoutServiceImpl.java:151)



Also, when I run a suite of tests in Intellij say at the level of the package where my unit test class resides (com.central.core.checkout.service), I get this same error. I have removed all other versions of easymock in .m2/repository to ensure there is no conflict.


com.central.core.checkout.service


.m2/repository



The concern is; why does this error only occur upon a mvn clean install (in Intellij or cmd line) and at a package level unit test run?


mvn clean install



I suppose what really concerns me, aside from differing results depending on how the test is run, is the the exception that is thrown:



java.lang.IllegalStateException: 2 matchers expected, 12 recorded.


java.lang.IllegalStateException: 2 matchers expected, 12 recorded.



tells me there are 2 matchers and 12 recorded. Is this including those created in other unit tests, almost as if spanning a test session? I find this difficult to believe as each unit test method creates a fresh mock @Before invocation.


2 matchers and 12 recorded


@Before



Added July 6th @ 15:21



So, to expedite this current build process and achieve no failing unit tests, I @Ignored this failing unit test and attempted a build. The build failed again but this time the preceding method was the problem child with a similar exception:


@Ignore



java.lang.IllegalStateException: 2 matchers expected, 12 recorded.
This exception usually occurs when matchers are mixed with raw values when recording a method:
foo(5, eq(6)); // wrong
You need to use no matcher at all or a matcher for every single param:
foo(eq(5), eq(6)); // right
foo(5, 6); // also right


java.lang.IllegalStateException: 2 matchers expected, 12 recorded.
This exception usually occurs when matchers are mixed with raw values when recording a method:
foo(5, eq(6)); // wrong
You need to use no matcher at all or a matcher for every single param:
foo(eq(5), eq(6)); // right
foo(5, 6); // also right



I tried a little experiment and @Ignored this current failing method and tried another build but before kinda knew the next preceding method in the class would be the problem child. Lo and behold, it was.


@Ignore




2 Answers
2



Are you sitting comfortably? Then I will begin...



So I posted this question and of course I kept trying for a solution. I exhausted all channels in fixing the unit tests so I decided to look at this from a different angle. The issue came to light when the development environment was built. I noticed there were two builds quite close together. The former succeeded but the latter failed with the afore mention exception.



Now, going all Columbo I needed to determine the differences between the two builds and it turned out it was one little unit test. This unit test was a straight forward unit test in that it needed no mocking of any sort. What was odd was the Easymock.eq was imported. Strange indeed and even stranger was that it was contained within an assertEquals statement where the 'expected' value was wrapped in this eq(). Yikes and in the wise words of Han Solo, "I've got a bad feeling about this".


Easymock.eq


assertEquals


eq()



I removed this import, together with the eq(), ran the unit test method in isolation...success. I then invoked a build with a test run. Success.


eq()



So, what I have learned from this is that using the Easymock.eq() method in the wrong context, in this case an Assert.assertEquals() really strange things happen but still not sure why. Even stranger was that running the unit test in isolation inside my IDE succeeded. :-/


Easymock.eq()


Assert.assertEquals()



I'll give you a little under the hood of EasyMock insight to help you understand.



When you use a matcher, it is stored in a ThreadLocal. So when the mocked method call actually occurs, a matcher for each parameter is in thread local. EasyMock removes them from there and creates a call expectation.


ThreadLocal



So, when too many matchers are recording, everything gets misaligned and weird things can happen. That's what the error message is saying. That 12 matchers were recorded but only 2 were expected since you have 2 arguments to your method.



Since Maven and IntelliJ are not forking a new VM between tests, the bad matchers were still there from one test to the other.






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

api-platform.com Unable to generate an IRI for the item of type

How to set up datasource with Spring for HikariCP?

Display dokan vendor name on Woocommerce single product pages