org.cleverbus.test.AbstractTest.java Source code

Java tutorial

Introduction

Here is the source code for org.cleverbus.test.AbstractTest.java

Source

/*
 * Copyright (C) 2015
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cleverbus.test;

import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.cleverbus.api.exception.ErrorExtEnum;
import org.cleverbus.api.route.CamelConfiguration;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.test.spring.CamelSpringJUnit4ClassRunner;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.Difference;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.XMLAssert;
import org.custommonkey.xmlunit.XMLUnit;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsEqual;
import org.joda.time.ReadableInstant;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.util.ReflectionUtils;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * Parent class for all tests with Apache Camel.
 *
 * @author <a href="mailto:petr.juza@cleverlance.com">Petr Juza</a>
 */
@RunWith(CamelSpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/META-INF/test_camel.xml" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class AbstractTest {

    private static boolean initAllRoutes = false;

    @Autowired
    private ModelCamelContext camelContext;

    @Autowired
    private ApplicationContext ctx;

    @Before
    public void configureXmlUnit() {
        XMLUnit.setIgnoreWhitespace(true);
    }

    @After
    public void validateMockito() {
        Mockito.validateMockitoUsage();
    }

    /**
     * Sets {@code true} if all routes should be initialized (=added into Camel context).
     * By default the value is {@code false} - only routes defined in {@link ActiveRoutes} annotation are initialized.
     * <p/>
     * Use {@link BeforeClass} annotation for setting this parameter.
     *
     * @param initAllRoutes {@code true} for initialization of all routes, otherwise {@code false}
     */
    public static void setInitAllRoutes(boolean initAllRoutes) {
        AbstractTest.initAllRoutes = initAllRoutes;
    }

    /**
     * Initializes selected routes for specific test.
     * Active route definitions are defined via {@link ActiveRoutes} annotation.
     *
     * @throws Exception when init fails
     */
    @Before
    public void initRoutes() throws Exception {
        getCamelContext().getShutdownStrategy().setTimeout(1);// no shutdown timeout:
        getCamelContext().getShutdownStrategy().setTimeUnit(TimeUnit.NANOSECONDS);
        getCamelContext().getShutdownStrategy().setShutdownNowOnTimeout(true);// no pending exchanges

        Map<String, Class<RoutesBuilder>> beans = new HashMap<String, Class<RoutesBuilder>>();

        String[] beanNames = getApplicationContext().getBeanDefinitionNames();

        for (String beanName : beanNames) {
            Class beanClass = getApplicationContext().getType(beanName);

            if (beanClass.isAnnotationPresent(CamelConfiguration.class)) {
                beans.put(beanName, beanClass);
            }
        }

        Set<Class> activeRoutesClasses = getActiveRoutes();

        for (Map.Entry<String, Class<RoutesBuilder>> entry : beans.entrySet()) {

            for (Class routesClass : activeRoutesClasses) {

                if (entry.getValue().isAssignableFrom(routesClass)) {
                    getCamelContext().addRoutes((RoutesBuilder) getApplicationContext().getBean(entry.getKey()));
                }
            }
        }
    }

    /**
     * Gets set of active route definitions which should be added into Camel context.
     *
     * @return set of classes
     */
    private Set<Class> getActiveRoutes() {
        Set<Class> routeClasses = new HashSet<Class>();

        Class<ActiveRoutes> annotationType = ActiveRoutes.class;

        Class<?> testClass = this.getClass();

        Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, testClass);
        if (declaringClass == null) {
            return routeClasses;
        }

        while (declaringClass != null) {
            ActiveRoutes activeRoutes = declaringClass.getAnnotation(annotationType);

            routeClasses.addAll(Arrays.asList(activeRoutes.classes()));

            declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
        }

        return routeClasses;
    }

    /**
     * Gets Camel context.
     *
     * @return Camel context
     */
    protected final ModelCamelContext getCamelContext() {
        return camelContext;
    }

    /**
     * Gets Spring context.
     *
     * @return Spring context
     */
    protected final ApplicationContext getApplicationContext() {
        return ctx;
    }

    /**
     * Sets value of private field.
     *
     * @param target the target object
     * @param name   the field name
     * @param value  the value for setting to the field
     */
    public static void setPrivateField(Object target, String name, Object value) {
        Field countField = ReflectionUtils.findField(target.getClass(), name);
        ReflectionUtils.makeAccessible(countField);
        ReflectionUtils.setField(countField, target, value);
    }

    /**
     * Same as {@link XMLAssert#assertXMLEqual(String, String)},
     * but with the specified node verified as XML using the same similarity technique,
     * not as text (which would have to match completely).
     *
     * @param control               the expected XML
     * @param test                  the actual XML being verified
     * @param innerXmlXpathLocation the XPath location of the element, that should be verified as XML, not as text
     * @throws org.xml.sax.SAXException can be thrown when creating {@link Diff} (e.g., if the provided XML is invalid)
     * @throws java.io.IOException      can be thrown when creating {@link Diff}
     */
    public static void assertXMLEqualWithInnerXML(String control, String test, final String innerXmlXpathLocation)
            throws SAXException, IOException {
        Diff myDiff = new Diff(control, test);
        myDiff.overrideDifferenceListener(new DifferenceListener() {
            @Override
            public int differenceFound(Difference difference) {
                if (innerXmlXpathLocation.equals(difference.getTestNodeDetail().getXpathLocation())) {
                    //use a custom verification for the encoded XML payload:
                    try {
                        Diff innerDiff = new Diff(difference.getControlNodeDetail().getValue(),
                                difference.getTestNodeDetail().getValue());
                        if (innerDiff.identical()) {
                            return RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
                        } else if (innerDiff.similar()) {
                            return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
                        } else {
                            fail("Inner encoded XML node at " + innerXmlXpathLocation + " doesn't match\n"
                                    + innerDiff);
                        }
                    } catch (Exception e) {
                        //shouldn't have a problem creating a Diff instance at this point,
                        // so just ignore and acknowledge the difference
                    }
                }
                // the default behavior is to acknowledge the difference:
                return RETURN_ACCEPT_DIFFERENCE;
            }

            @Override
            public void skippedComparison(Node control, Node test) {
            }
        });

        assertXMLEqual(myDiff, true);
    }

    /**
     * Shorthand for {@link CoreMatchers#is(org.hamcrest.Matcher) is}
     * ({@link #equalDateTime(org.joda.time.ReadableInstant) equalDateTime}(expected)).
     */
    public static <T extends ReadableInstant> Matcher<T> isDateTime(final T expected) {
        return is(equalDateTime(expected));
    }

    /**
     * Creates a matcher that compares JodaTime {@link ReadableInstant} objects based on their millis only,
     * not Chronology as the default equals() does.
     * This comparison uses {@link ReadableInstant#isEqual(org.joda.time.ReadableInstant)}
     * instead of default {@link ReadableInstant#equals(Object)}.
     *
     * @param expected the expected ReadableInstant
     * @param <T>      any class implementing ReadableInstant
     * @return matcher for the usual Hamcrest matcher chaining
     * @see CoreMatchers#equalTo(Object)
     * @see ReadableInstant#equals(Object)
     * @see ReadableInstant#isEqual(org.joda.time.ReadableInstant)
     */
    public static <T extends ReadableInstant> Matcher<T> equalDateTime(final T expected) {
        return new IsEqual<T>(expected) {
            @Override
            public boolean matches(Object actualValue) {
                if (expected == null) {
                    return actualValue == null;
                } else {
                    return actualValue instanceof ReadableInstant
                            && expected.isEqual((ReadableInstant) actualValue);
                }
            }
        };
    }

    /**
     * Returns processor that throws specified exception.
     *
     * @param exc the exception
     * @return processor that throws specified exception
     */
    protected static Processor throwException(final Exception exc) {
        return new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                throw exc;
            }
        };
    }

    /**
     * Mocks a hand-over-type endpoint (direct, direct-vm, seda or vm)
     * by simply providing the other (consumer=From) side connected to a mock.
     * <p/>
     * There should be no consumer existing, i.e., the consumer route should not be started.
     *
     * @param uri the URI a new mock should consume from
     * @return the mock that is newly consuming from the URI
     */
    protected MockEndpoint mockDirect(final String uri) throws Exception {
        return mockDirect(uri, null);
    }

    /**
     * Same as {@link #mockDirect(String)}, except with route ID to be able to override an existing route with the mock.
     *
     * @param uri     the URI a new mock should consume from
     * @param routeId the route ID for the new mock route
     *                (existing route with this ID will be overridden by this new route)
     * @return the mock that is newly consuming from the URI
     */
    protected MockEndpoint mockDirect(final String uri, final String routeId) throws Exception {
        // precaution: check that URI can be mocked by just providing the other side:
        org.junit.Assert.assertThat(uri,
                anyOf(startsWith("direct:"), startsWith("direct-vm:"), startsWith("seda:"), startsWith("vm:")));

        // create the mock:
        final MockEndpoint createCtidMock = getCamelContext().getEndpoint("mock:" + uri, MockEndpoint.class);
        // redirect output to this mock:
        getCamelContext().addRoutes(new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                RouteDefinition routeDef = from(uri);

                if (routeId != null) {
                    routeDef.routeId(routeId);
                }

                routeDef.to(createCtidMock);
            }
        });

        return createCtidMock;
    }

    /**
     * Asserts {@link ErrorExtEnum error codes}.
     *
     * @param error the actual error code
     * @param expError the expected error code
     */
    protected void assertErrorCode(ErrorExtEnum error, ErrorExtEnum expError) {
        assertThat(error.getErrorCode(), CoreMatchers.is(expError.getErrorCode()));
    }
}