Unit testing of a class with StaticLoggerBinder - unit-testing

I do have a simple class like this:
package com.example.howtomocktest
import groovy.util.logging.Slf4j
import java.nio.channels.NotYetBoundException
#Slf4j
class ErrorLogger {
static void handleExceptions(Closure closure) {
try {
closure()
}catch (UnsupportedOperationException|NotYetBoundException ex) {
log.error ex.message
} catch (Exception ex) {
log.error 'Processing exception {}', ex
}
}
}
And I would like to write a test for it, here is a skeleton:
package com.example.howtomocktest
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static com.example.howtomocktest.ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
private static final UNSUPPORTED_EXCEPTION = { throw UnsupportedOperationException }
private static final NOT_YET_BOUND = { throw NotYetBoundException }
private static final STANDARD_EXCEPTION = { throw Exception }
private Logger logger = Mock(Logger.class)
def setup() {
}
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions {UNSUPPORTED_EXCEPTION}
then:
notThrown(UnsupportedOperationException)
1 * logger.error(_ as String) // doesn't work
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions {NOT_YET_BOUND}
then:
notThrown(NotYetBoundException)
1 * logger.error(_ as String) // doesn't work
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions {STANDARD_EXCEPTION}
then:
notThrown(STANDARD_EXCEPTION)
1 * logger.error(_ as String) // doesn't work
}
}
The logger in ErrorLogger class is provided by StaticLoggerBinder, so my question is - how do I make it work so that those checks "1 * logger.error(_ as String)" would work? I can't find a proper way of mocking that logger inside of ErrorLogger class. I have thought about reflection and somehow accessing it, furthermore there was an idea with mockito injection (but how to do that if reference to an object is not even present in that class because of that Slf4j annotation!) Thanks in advance for all your feedback and advices.
EDIT: Here is an output of a test, even 1*logger.error(_) doesn't work.
Too few invocations for:
1*logger.error() (0 invocations)
Unmatched invocations (ordered by similarity):

What you would need to do is to replace the log field generated by the #Slf4j AST transformation with your mock.
However, this is not so easy to achieve, since the generated code is not really test-friendly.
A quick look at the generated code reveals that it corresponds to something like this:
class ErrorLogger {
private final static transient org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(ErrorLogger)
}
Since the log field is declared as private final it is not so easy to replace the value with your mock. It actually boils down to the exact same problem as described here. In addition, usages of this field is wrapped in isEnabled() methods, so for instance every time you invoke log.error(msg) it is replaced with:
if (log.isErrorEnabled()) {
log.error(msg)
}
So, how to solve this? I would suggest that you register an issue at the groovy issue tracker, where you ask for a more test-friendly implementation of the AST transformation. However, this won't help you much right now.
There are a couple of work-around solutions to this that you might consider.
Set the new field value in your test using the "awful hack" described in the stack overflow question mentioned above. I.e. make the field accessible using reflection and set the value. Remember to reset the value to the original during cleanup.
Add a getLog() method to your ErrorLogger class and use that method for access instead of direct field access. Then you may manipulate the metaClass to override the getLog() implementation. The problem with this approach is that you would have to modify the production code and add a getter, which kind of defies the purpose of using #Slf4j in the first place.
I'd also like to point out that there are several problems with your ErrorLoggerSpec class. These are hidden by the problems you've already encountered, so you would probably figure these out by yourself when they manifested themselves.
Even though it is a hack, I'll only provide code example for the first suggestion, since the second suggestion modifies the production code.
To isolate the hack, enable simple reuse and avoid forgetting to reset the value, I wrote it up as a JUnit rule (which can also be used in Spock).
import org.junit.rules.ExternalResource
import org.slf4j.Logger
import java.lang.reflect.Field
import java.lang.reflect.Modifier
public class ReplaceSlf4jLogger extends ExternalResource {
Field logField
Logger logger
Logger originalLogger
ReplaceSlf4jLogger(Class logClass, Logger logger) {
logField = logClass.getDeclaredField("log");
this.logger = logger
}
#Override
protected void before() throws Throwable {
logField.accessible = true
Field modifiersField = Field.getDeclaredField("modifiers")
modifiersField.accessible = true
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)
originalLogger = (Logger) logField.get(null)
logField.set(null, logger)
}
#Override
protected void after() {
logField.set(null, originalLogger)
}
}
And here is the spec, after fixing all the small bugs and adding this rule. Changes are commented in the code:
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
// NOTE: These three closures are changed to actually throw new instances of the exceptions
private static final UNSUPPORTED_EXCEPTION = { throw new UnsupportedOperationException() }
private static final NOT_YET_BOUND = { throw new NotYetBoundException() }
private static final STANDARD_EXCEPTION = { throw new Exception() }
private Logger logger = Mock(Logger.class)
#Rule ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(ErrorLogger, logger)
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions UNSUPPORTED_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(UnsupportedOperationException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions NOT_YET_BOUND // Changed: used to be a closure within a closure!
then:
notThrown(NotYetBoundException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions STANDARD_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(Exception) // Changed: you added the closure field instead of the class here
//1 * logger.isErrorEnabled() >> true // this call is NOT added by the AST transformation -- perhaps a bug?
1 * logger.error(_ as String, _ as Exception) // in this case, both a message and the exception is specified
}
}

If you are using Spring, you have acces to OutputCaptureRule
#Rule
OutputCaptureRule outputCaptureRule = new OutputCaptureRule()
def test(){
outputCaptureRule.getAll().contains("<your test output>")
}

Related

Spock unit testing assert log calls and see output

I am using spock to test Java Spring Boot code. It gets a logback logger over the lombok #Slf4j annotation.
Dummy class with log call
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
#Slf4j
#Component
public class Clazz {
public void method() {
// ... code
log.warn("message", new RuntimeException());
}
}
The Spock Spec
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
#Slf4j
class LogSpec extends Specification {
Clazz clazz = new Clazz()
private Logger logger = Mock(Logger.class)
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning ia logged"() {
given: "expected message"
when: "when calling the method"
clazz.method()
then: "a warning is logged"
1 * logger.warn(_, _) >> {
msg, ex -> log.warn(msg, ex)
}
}
}
Helper to switch the real with the mock logger taken from this answer.
import org.junit.rules.ExternalResource
import org.slf4j.Logger
import java.lang.reflect.Field
import java.lang.reflect.Modifier
/**
* Helper to exchange loggers set by lombok with mock logger
*
* allows to assert log action.
*
* Undos change after test to keep normal logging in other tests.
*
* code from this answer answer
*/
class ReplaceSlf4jLogger extends ExternalResource {
Field logField
Logger logger
Logger originalLogger
ReplaceSlf4jLogger(Class logClass, Logger logger) {
logField = logClass.getDeclaredField("log")
this.logger = logger
}
#Override
protected void before() throws Throwable {
logField.accessible = true
Field modifiersField = Field.getDeclaredField("modifiers")
modifiersField.accessible = true
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)
originalLogger = (Logger) logField.get(null)
logField.set(null, logger)
}
#Override
protected void after() {
logField.set(null, originalLogger)
}
}
I would like to test log calls, but still see the log message.
I am using the solution from this answer, it works for the assertion but I don't see the log because it is a mock call.
I came up with this solution, which does a the call with the logger of the groovy spec.
1 * logger.warn(_ , _) >> {
msg, ex -> log.warn(msg, ex)
}
But I find it verbose, any idea how I could create a helper function for it. I am not very familiar with functional groovy and moving this code into a function is not working.
I also tried a Spy instead of a Mock but that gets me an error because the logger class is final.
import ch.qos.logback.classic.Logger
private Logger logger = Spy(Logger.class)
>> org.spockframework.mock.CannotCreateMockException: Cannot create mock
for class ch.qos.logback.classic.Logger because Java mocks cannot mock final classes.
If the code under test is written in Groovy, use a Groovy mock.
Logger class at runtime
package ch.qos.logback.classic;
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
Thanks
Actually in your MCVE you expect the warn(_, _) method to be called with two parameters, but you are not logging like that in Clazz, so either you have to change Clazz to also log an exception or change the test to expect a method call with one parameter. I am doing the latter here.
As for your problem, the solution is to not use a mock but a spy. You need to tell Spock which exact class you want to spy on, though. This is because you cannot spy on an interface type, of course. I have chosen a SimpleLogger (change to whatever you use in your application).
package de.scrum_master.stackoverflow
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.impl.SimpleLogger
import spock.lang.Specification
#Slf4j
class LombokSlf4jLogTest extends Specification {
SimpleLogger logger = Spy(constructorArgs: ["LombokSlf4jLogTest"])
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning is logged"() {
when: "when calling the method"
new Clazz().method()
then: "a warning is logged"
1 * logger.warn(_)
}
}
Update: For what it is worth, here is a version which also works with LogBack-Classic instead of Log4J-Simple on the classpath. Instead of directly spying on the final class, let's just spy on a Groovy #Delegate:
Please also note that I changed to *_ in the test so as to accommodate to warn calls with an arbitrary number of arguments.
package de.scrum_master.stackoverflow
import groovy.util.logging.Slf4j
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
#Slf4j
class LombokSlf4jLogTest extends Specification {
def logger = Spy(new LoggerDelegate(originalLogger: log))
#Rule
ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(Clazz, logger)
def "warning is logged"() {
when: "when calling the method"
new Clazz().method()
then: "a warning is logged"
1 * logger.warn(*_)
true
}
static class LoggerDelegate {
#Delegate Logger originalLogger
}
}
Update 2020-01-23: I just found this one again and noticed that I forgot to explain why the #Delegate solution works: because a Groovy delegate automatically implements all interfaces which the class of the delegate instance also implements by default. In this case the logger field is declared as Logger which is an interface type. This is also why e.g. Log4J or Logback instances can be used based on the configuration. The trick of mocking or spying on a final class type not implementing an interface or used explicitly with its class name would not work in that case because the delegating class would not (and could not) be a subclass of the final class type and thus could not be injected instead of the delegate.
Update 2020-04-14: I did not mention before that if you don't want to spy on a real logger but simply use a dummy you can check interactions on, just use a regular Spock mock on the org.slf4j.Logger interface: def logger = Mock(Logger) That is actually the simplest solution and you don't clutter your test log with exception stack traces and other log output. I was so focused on helping the OP with his spy solution that I did not mention this before.
These is one more "creative" approach for this kind of issue I would like to share.
Instead of mocking the logger you can create an "artificial" appender, add it programmatically to the logger in the class under-test.
The appender will keep track of the logged messages and during the verification phase you will get those logged message and verify
You'll end up with something like this (pseudo code just to show the idea):
class MsgTrackingAppender implements Appender { // Appender of your logging system
private List<LogEvent> events = new ArrayList<>();
public void doAppend(LogEvent evt) {
events.add(evt);
}
public List<LogEvent> getEvents() {
return events;
}
}
// now in test you can do:
class LogSpec extends Specification {
def "test me"() {
given:
Clazz underTest = Clazz()
MsgTrackingAppender appender = new MsgTrackingAppender()
LogFactory.getLogger(Clazz.class).addAppender(appender)
when:
underTest.method()
then:
appender.events.size == 1
appender.events[0].level == Level.WARN
appender.events[0].message == ... // verify whatever you want on the messages
}
}
IMO this approach is easier to use than extensive mocking but its a matter of taste of course.

Meaning of closure in then clause in spock

We are in process of writing Unit test cases using Spock, I am not able to understand the following code snippet in then section varifying the declaration,
then:
1 * service.fraudMigrationOnboardingService.onboard(_) >>
{merchantId -> successCallBack.call(response)}
what is the meaning of the above code.
Because your question is lacking detail, I have to speculate and make an educated guess about your test. :-/
So you have a service with a member or getter fraudMigrationOnboardingService.
fraudMigrationOnboardingService has a method onboard taking a single parameter.
Obviously fraudMigrationOnboardingService is a mock or spy, which is why you can check interactions like 1 * ... on it.
The developer who wrote this test and whom, as it seems, you are too shy to ask about its meaning or who has left your company, wanted something specific to happen when method onboard(_) is called (probably by service) during the test: a call-back method call. Thus she declared the method stub { merchantId -> successCallBack.call(response) } as a replacement for what onboard(_) would normally do in this case. In a spy it would execute the original method, in a mock it would no nothing at all. But obviously that is not the desired behaviour, maybe because the test relies on different behavious later on.
In general, I think a test which is hard to read should be refactored, but anyway, here I am replicating your situation:
package de.scrum_master.stackoverflow
import spock.lang.Specification
class DummyTest extends Specification {
static class Service {
FraudMigrationOnboardingService fraudMigrationOnboardingService
void doSomething(String name) {
println "Doing something"
fraudMigrationOnboardingService.onboard(name)
}
}
static class FraudMigrationOnboardingService {
void onboard(String name) {
println "On-boarding $name"
}
}
static class SuccessCallBack {
void call(int httpResponse) {
println "Callback HTTP response = $httpResponse"
}
}
def "Some service test"() {
given:
def onboardingService = Mock(FraudMigrationOnboardingService)
def service = new Service(fraudMigrationOnboardingService: onboardingService)
def successCallBack = new SuccessCallBack()
def response = 200
when:
service.doSomething("ACME Inc.")
then:
1 * service.fraudMigrationOnboardingService.onboard(_) >>
{ merchantId -> successCallBack.call(response) }
}
}
The console log says:
Doing something
Callback HTTP response = 200
If you would comment out >> { merchantId -> successCallBack.call(response) }, it would only print
Doing something
for a mock and if you also change the Mock(FraudMigrationOnboardingService) into a Spy(FraudMigrationOnboardingService) it would print
Doing something
On-boarding ACME Inc.
Update: Maybe you still don't understand what the closure means, I am not sure. So I will explain it a bit more: As I said, it is just a stub for the onboard(String) method. The method parameter is mapped to merchantId but not used in the stubbed method. Instead the callback is triggered.

Test case for Struts2 2.3.24 using Strut2SpringTestCase (request object is coming as null)

I am trying to write unit test cases for my Struts2 action classes. My Test class extends SpringStrutsTestCase class. I am able to set the request object and able to get the action and action is also getting called but when in action it tries to get the parameters set in request object it throws null pointer exception i.e. request object is going as null. Below is my what my test class looks like. Any help is really appreciated.
import org.apache.struts2.StrutsSpringTestCase;
import org.junit.Test;
import com.opensymphony.xwork2.ActionProxy;
public class testClass extends StrutsSpringTestCase {
#Test
public void test1() throws Exception {
try {
request.setParameter("p1", "v1");
request.setParameter("p2", "v2");
ActionProxy proxy = getActionProxy("/actionName");
MyActionClass loginAction = (MyActionClass) proxy.getAction();
loginAction.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public String[] getContextLocations() {
String[] arr = new String[] { "one.xml", "two.xml", "three.xml" };
return arr;
}
}
Here is my action class.
public class MyAction extends ActionSupport{
private String p1;
private String p2;
/*
Gettere and Setters of p1 and p2
*/
public String execute() throws Exception {
// return "success";
logger.info("Login Action Called");
String pv1= (String) request.getParameter("p1");// If I get value using this.pv1 it works fine but with this code it doesn't.
String pv2= (String) request.getParameter("p2");
return "success";
}
}
In order to test an action call you need to call execute method of ActionProxy. By calling execute of your action you are just invoking that particular method of the action class and not S2 action along with the interceptors, results, etc.
The correct way would be:
ActionProxy proxy = getActionProxy("/actionName");
proxy.execute();
BTW if you're using JUnit 4 there is StrutsSpringJUnit4TestCase which you should use instead of StrutsSpringTestCase.

Mock static method with GroovyMock or similar in Spock

First-timer here, apologies if I've missed anything.
I'm hoping to get around a call to a static method using Spock. Feedback would be great
With groovy mocks, I thought I'd be able to get past the static call but haven't found it.
For background, I'm in the process of retrofitting tests in legacy java. Refactoring is prohibited. I'm using spock-0.7 with groovy-1.8.
The call to the static method is chained with an instance call in this form:
public class ClassUnderTest{
public void methodUnderTest(Parameter param){
//everything else commented out
Thing someThing = ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(param);
}
}
staticMethod returns an instance of ClassWithStatic
instanceMethod returns the Thing needed in the rest of the method
If I directly exercise the global mock, it returns the mocked instance ok:
def exerciseTheStaticMock(){
given:
def globalMock = GroovyMock(ClassWithStatic,global: true)
def instanceMock = Mock(ClassWithStatic)
when:
println(ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(testParam))
then:
interaction{
1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
1 * instanceMock.instanceMethod(_) >> returnThing
}
}
But if I run the methodUnderTest from the ClassUnderTest:
def failingAttemptToGetPastStatic(){
given:
def globalMock = GroovyMock(ClassWithStatic,global: true)
def instanceMock = Mock(ClassWithStatic)
ClassUnderTest myClassUnderTest = new ClassUnderTest()
when:
myClassUnderTest.methodUnderTest(testParam)
then:
interaction{
1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
1 * instanceMock.instanceMethod(_) >> returnThing
}
}
It throws down a real instance of ClassWithStatic that goes on to fail in its instanceMethod.
Spock can only mock static methods implemented in Groovy. For mocking static methods implemented in Java, you'll need to use a tool like GroovyMock , PowerMock or JMockit.
PS: Given that these tools pull of some deep tricks in order to achieve their goals, I'd be interested to hear if and how well they work together with tests implemented in Groovy/Spock (rather than Java/JUnit).
Here is how I solved my similar issue (mocking a static method call which is being called from another static class) with Spock (v1.0) and PowerMock (v1.6.4)
import org.junit.Rule
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import spock.lang.Specification
import static org.powermock.api.mockito.PowerMockito.mockStatic
import static org.powermock.api.mockito.PowerMockito.when
#PrepareForTest([YourStaticClass.class])
#PowerMockIgnore(["javax.xml.*", "ch.qos.logback.*", "org.slf4j.*"])
class YourSpockSpec extends Specification {
#Rule
Powermocked powermocked = new Powermocked();
def "something something something something"() {
mockStatic(YourStaticClass.class)
when: 'something something'
def mocked = Mock(YourClass)
mocked.someMethod(_) >> "return me"
when(YourStaticClass.someStaticMethod(xyz)).thenReturn(mocked)
then: 'expect something'
YourStaticClass.someStaticMethod(xyz).someMethod(abc) == "return me"
}
}
The #PowerMockIgnore annotation is optional, only use it if there is some conflicts with existing libraries
A workaround would be to wrap the static method call into an instance method.
class BeingTested {
public void methodA() {
...
// was:
// OtherClass.staticMethod();
// replaced with:
wrapperMethod();
...
}
// add a wrapper method for testing purpose
void wrapperMethod() {
OtherClass.staticMethod();
}
}
Now you can use a Spy to mock out the static method.
class BeingTestedSpec extends Specification {
#Subject BeingTested object = new BeingTested()
def "test static method"() {
given: "a spy into the object"
def spyObject = Spy(object)
when: "methodA is called"
spyObject.methodA()
then: "the static method wrapper is called"
1 * spyObject.wrapperMethod() >> {}
}
}
You can also stub in canned response for the wrapper method if it's supposed to return a value. This solution uses only Spock built-in functions and works with both Java and Groovy classes without any dependencies on PowerMock or GroovyMock.
The way I've gotten around static methods in Groovy/Spock is by creating proxy classes that are substituted out in the actual code. These proxy classes simply return the static method that you need. You would just pass in the proxy classes to the constructor of the class you're testing.
Thus, when you write your tests, you'd reach out to the proxy class (that will then return the static method) and you should be able to test that way.
I have recently found 'spock.mockfree' package, it helps mocking final classes and static classes/methods.
It is quite simple as with this framework, in this case, you would need only to Spy() the class under test and #MockStatic the static method you need.
Example:
We used a static method returnA of StaticMethodClass class
public class StaticMethodClass {
public static String returnA() {
return "A";
}
}
here is the calling code
public class CallStaticMethodClass {
public String useStatic() {
return StaticMethodClass.returnA();
}
}
Now we need to test the useStatic method of CallStaticMethodClass class But spock itself does not support mock static methods, and we support
class CallStaticMethodClassTest extends Specification {
def 'call static method is mocked method'() {
given:
CallStaticMethodClass callStaticMethodClass = Spy()
println("useStatic")
expect:
callStaticMethodClass.useStatic() == 'M'
}
#MockStatic(StaticMethodClass)
public static String returnA() {
return "M";
}
}
We use the #MockStatic annotation to mark which class needs to be mocked
Directly implement the static method that requires mocking under it, the method signature remains the same, but the implementation is different.
Link to the framework:
https://github.com/sayweee/spock-mockfree/blob/498e09dc95f841c4061fa8224fcaccfc53904c67/README.md

Get/save parameters to an expected JMock method call?

I want to test an "Adapter" object that when it receives an xml message,
it digest it to a Message object, puts message ID + CorrelationID both
with timestamps and forwards it to a Client object.=20
A message can be correlated to a previous one (e.g. m2.correlationID =3D
m1.ID).
I mock the Client, and check that Adapter successfully calls
"client.forwardMessage(m)" twice with first message with null
correlationID, and a second with a not-null correlationID.
However, I would like to precisely test that the correlationIDs are set
correctly, by grabing the IDs (e.g. m1.ID).
But I couldn't find anyway to do so.
There is a jira about adding the feature, but no one commented and it
is unassigned.
Is this really unimplemented?
I read about the alternative of redesigning the Adapter to use an
IdGenerator object, which I can stub, but I think there will be too many
objects.=20
Don't you think it adds unnecessary complexity to split objects to a so
fine granularity?
Thanks, and I appreciate any comments :-)
Tayeb
You could use a matcher (http://code.google.com/p/hamcrest) to check whether you get the correct arguments passed into your method. Note you'll need to statically import your matcher (HasCorrelationId in my example).
The example below asserts that client.forwardMessage(m) is called once with a message with null correlationId and once with a message with correlationId="abc".
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class HasCorrelationId extends TypeSafeMatcher<Message> {
private final Object expectedCorrelationId;
public HasCorrelationId(Object expectedCorrelationId) {
this.expectedCorrelationId = expectedCorrelationId;
}
#Override
public boolean matchesSafely(Message message) {
if (expectedCorrelationId==message.getCorrelationId()) return true;
return expectedCorrelationId==null? false: expectedCorrelationId.equals(message.getCorrelationId());
}
public void describeTo(Description description) {
description.appendText("hasCorrelationId("+expectedCorrelationId+")");
}
public static HasCorrelationId hasCorrelationId(Object correlationId) {
return new HasCorrelationId(correlationId);
}
}
... and now for the test:
public class MyTest {
private Mockery context = new Mockery();
#Test
public void testCorrelationId() {
final Client client = context.mock(Client.class);
final Message message1 = context.mock(Message.class,"message1");
final Message message2 = context.mock(Message.class,"message2");
context.checking(new Expectations(){{
allowing(message1).getCorrelationId();will(returnValue(null));
allowing(message2).getCorrelationId();will(returnValue("abc"));
one(client).forwardMessage(with(hasCorrelationId(null)));
one(client).forwardMessage(with(hasCorrelationId("abc")));
}});
client.forwardMessage(message1);
client.forwardMessage(message2);
}
}
We've made some effort to simplify writing matchers in Hamcrest 1.2. There's a new FeatureMatcher which requires less work.