0

I'm trying to test a Camel route which uses from(x).to(y).log("SuccessKey123") and onException(HttpOperationFailedException.class).log("ErrorKey123").

How can I test that Camel logs "SuccessKey123" when the message was successfully processed or it logs "ErrorKey123" if HttpOperationFailedException is thrown?

My RouteBuilder():

@Component
public class myHttp4RouteBuilder extends SpringRouteBuilder {
    public static final ID = "foo";

    @Override
    public void configure() throws Exception {

        onException(HttpOperationFailedException.class)
                .log("ErrorKey123")
                .to(ERROR_QUEUE)
                .handled(true);

        from(AWS_SQS_ENDPOINT)
                .to(REST_API_ENDPOINT)
                .log("SuccessKey123");
    }
}

Testclass:

public class myHttp4RouteBuilderTest {

    @Produce(uri = MOCK_ROUTE_FROM)
    protected ProducerTemplate template;

    @EndpointInject(uri = MOCK_ROUTE_TO)
    private MockEndpoint mockEndpoint;

    @Autowired
    private CamelContext camelContext;

    @Before
    public void setup() throws Exception{
        RouteDefinition rd = camelContext.getRouteDefinition(myHttp4RouteBuilder.ID);
        rd.adviceWith(camelContext, new AdviceWithRouteBuilder() {
            @Override
            public void configure() throws Exception {
                replaceFromWith(MOCK_ROUTE_FROM);

                weaveByToUri(ERROR_QUEUE)
                        .replace()
                        .to(MOCK_ROUTE_TO);
            }
        });
    }

    @Test
    @DirtiesContext
    public void testSuccess() throws Exception {
            // throw an HttpOperationFailedException
        mockEndpoint.whenAnyExchangeReceived(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                throw new HttpOperationFailedException("Exception", 400, null, null, null, null);
            }
        });


        //
        //
        // How can I test here that camel logs "ErrorKey123"
        //
        //


        template.sendBody(MOCK_ROUTE_FROM, "some content");

        mockEndpoint.assertIsSatisfied();

    }
}

Thank you very much!

Mr. DevOps
  • 11
  • 1
  • 5
  • Also see this answer: https://stackoverflow.com/questions/1827677/how-to-do-a-junit-assert-on-a-message-in-a-logger – Steve Huston Jul 19 '17 at 13:23

5 Answers5

3

Camel uses slf4j so you can just add some test appender on setup to the required logger and check what was logged after that (or even mock appender interface)

Vyacheslav Enis
  • 1,676
  • 12
  • 17
1

I got it ;-) You put me to the right way. Thanks!

This is my solution:

First: create a custom Appender

package de.example.test;

import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.event.LoggingEvent;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;


@Plugin(name="myAppenderForTesting", category="Core", elementType="appender", printObject=true)
public class MyAppenderForTesting extends AbstractAppender {

    /** Here we collect all log messages */
    public static List<LogEvent> logEvents = new ArrayList<>();

    protected MyAppenderForTesting(String name, Filter filter, Layout<? extends Serializable> layout, final boolean ignoreExceptions) {
        super(name, filter, layout, ignoreExceptions);
    }
    @PluginFactory
    public static MyAppenderForTesting createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Layout") Layout<? extends Serializable> layout,
            @PluginElement("Filter") final Filter filter,
            @PluginAttribute("otherAttribute") String otherAttribute) {

        return new MyAppenderForTesting(name, filter, layout, true);

    }
    @Override
    public void append(LogEvent event) {
        try {
            logEvents.add(event);
        } catch (Exception ex) {
            if (!ignoreExceptions()) {
                throw new AppenderLoggingException(ex);
            }
        } finally {

        }
    }

    /**
     * Clear log messages
     */
    public static void clean() {
        logEvents.clear();
    }
}

Short explanation: with append() method we add each log event to a public static variable logEvents. Later in test we can access logEvents.

It was a little bit difficult to get this appender working with log4j. In my case I created a log4j2.xml in the test resources src\test\resources\log4j2.xml.

<?xml version="1.0" encoding="UTF-8" ?>

<Configuration packages="de.example">
    <Appenders>
        <myAppenderForTesting name="myAppenderForTesting">
            <PatternLayout alwaysWriteExceptions="false" pattern="%d{dd.MM.yyyy HH:mm:ss} %-5p %t [%C{1}.%M:%L] %m %ex{10000}%n" />
        </myAppenderForTesting>
    </Appenders>

    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="myAppenderForTesting"/>
        </Root>
    </Loggers>
</Configuration>

In my test classes I can access directly to MyAppenderForTesting.logEvents. For example

    for (LogEvent event : MyAppenderForTesting.logEvents) {
        String message = event.getMessage().toString();
        if (message.contains(search)) {
            // do somethind
        }
    }
Mr. DevOps
  • 11
  • 1
  • 5
1

A different approach could be to use a log listener to collect the messages and assert them afterwards:

// adding log listener
Set<String> logMessages = new HashSet<>();
((SpringBootCamelContext) camelContext)
    .addLogListener((Exchange exchange, CamelLogger camelLogger, String message) -> {
                        logMessages.add(message);
                        return message;
    });
    
// others test initializations...

// asserting the expected log message
assertThat(logMessages.stream()
    .filter(m -> m.contains("looking for this message")).count()).isEqualTo(1);
Pasi Österman
  • 2,002
  • 1
  • 6
  • 13
diego
  • 11
  • 2
0

You can also use Camel's advice-with and then mock/replace those log endpoints with a mock etc, and then just assert that Camel routed a message to those depending on what you do.

Claus Ibsen
  • 56,060
  • 7
  • 50
  • 65
  • Hmm, my goal is to test the log message so I can grep for it. This is necessary for monitoring so I have to write a test that a certain message is logged. – Mr. DevOps Jul 20 '17 at 21:15
0

I agree with Claus Ibsen's answer. You could use AdviceWith and weaveByType(LogDefinition.class).selectIndex(...) to pinpoint the logging you expect.

Old thread but it has a high visibility, so I hope this answer helps someone.

e.g.

@SpringBootTest
@CamelSpringBootTest
public class MyRouteTest {
    
    @Autowired
    protected CamelContext context;

    @EndpointInject("mock:successRoute")
    private MockEndpoint successRouteMockEndpoint;

    @EndpointInject("mock:failRoute")
    private MockEndpoint failRouteMockEndpoint;

    ...

    @Test
    public void Given_SuccessfulCall_ThenLogSuccess() throws Exception {
        AdviceWith.adviceWith(context, myRouteId,
            a -> a.weaveByType(LogDefinition.class).selectIndex(1).replace().to(successRouteMockEndpoint));

        // directives to mock a successful response

        successRouteMockEndpoint.expectedMessageCount(1);
        failRouteMockEndpoint.expectedMessageCount(0);
        
        // trigger route

        successRouteMockEndpoint.assertIsSatisfied();
        failRouteMockEndpoint.assertIsSatisfied();
    }

    @Test
    public void Given_UnsuccessfulCall_ThenLogFailure() throws Exception {
        AdviceWith.adviceWith(context, myRouteId,
            a -> a.weaveByType(LogDefinition.class).selectIndex(0).replace().to(failRouteMockEndpoint));

        // directives to mock an unsuccessful response

        successRouteMockEndpoint.expectedMessageCount(0);
        failRouteMockEndpoint.expectedMessageCount(1);
        
        // trigger route

        successRouteMockEndpoint.assertIsSatisfied();
        failRouteMockEndpoint.assertIsSatisfied();
    }
}
Rocinante
  • 33
  • 1
  • 7