package au.com.dius.pact.provider.junit; import au.com.dius.pact.model.Pact; import au.com.dius.pact.provider.junit.loader.PactBroker; import au.com.dius.pact.provider.junit.loader.PactFolder; import au.com.dius.pact.provider.junit.loader.PactLoader; import au.com.dius.pact.provider.junit.loader.PactSource; import au.com.dius.pact.provider.junit.target.HttpTarget; import au.com.dius.pact.provider.junit.target.Target; import au.com.dius.pact.provider.junit.target.TestTarget; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.junit.runners.ParentRunner; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; /** * JUnit Runner runs pacts against provider * To set up name of tested provider use {@link Provider} annotation * To point on pact's source use {@link PactBroker}, {@link PactFolder} or {@link PactSource} annotations * <p> * To point provider for testing use combination of {@link Target} interface and {@link TestTarget} annotation * There is out-of-the-box implementation of {@link Target}: * {@link HttpTarget} that will play interaction from pacts as http request and check http responses * <p> * Runner supports: * - {@link org.junit.BeforeClass}, {@link org.junit.AfterClass} and {@link org.junit.ClassRule} annotations, * that will be run once - before/after whole contract test suite * <p> * - {@link org.junit.Before}, {@link org.junit.After} and {@link org.junit.Rule} annotations, * that will be run before/after each test of interaction * <b>WARNING:</b> please note, that only {@link org.junit.rules.TestRule} is possible to use with this runner, * i.e. {@link org.junit.rules.MethodRule} <b>IS NOT supported</b> * <p> * - {@link State} - before each interaction that require state change, * all methods annotated by {@link State} with appropriate state listed will be invoked */ public class PactRunner extends ParentRunner<InteractionRunner> { private static final Logger LOGGER = LoggerFactory.getLogger(PactRunner.class); private final List<InteractionRunner> child; public PactRunner(final Class<?> clazz) throws InitializationError { super(clazz); final Provider providerInfo = clazz.getAnnotation(Provider.class); if (providerInfo == null) { throw new InitializationError("Provider name should be specified by using " + Provider.class.getName() + " annotation"); } final String serviceName = providerInfo.value(); final Consumer consumerInfo = clazz.getAnnotation(Consumer.class); final String consumerName = consumerInfo != null ? consumerInfo.value() : null; final TestClass testClass = new TestClass(clazz); this.child = new ArrayList<>(); final List<Pact> pacts; try { pacts = getPactSource(testClass).load(serviceName).stream() .filter(p -> consumerName == null || p.getConsumer().getName().equals(consumerName)) .collect(Collectors.toList()); } catch (final IOException e) { throw new InitializationError(e); } if (pacts == null || pacts.isEmpty()) { throw new InitializationError("Did not find any pact files for provider " + providerInfo.value()); } for (final Pact pact : filterPacts(pacts)) { this.child.add(new InteractionRunner(testClass, pact)); } } protected List<Pact> filterPacts(List<Pact> pacts){ return pacts; } @Override protected List<InteractionRunner> getChildren() { return child; } @Override protected Description describeChild(final InteractionRunner child) { return child.getDescription(); } @Override protected void runChild(final InteractionRunner interaction, final RunNotifier notifier) { interaction.run(notifier); } protected PactLoader getPactSource(final TestClass clazz) throws InitializationError { final PactSource pactSource = clazz.getAnnotation(PactSource.class); final List<Annotation> pactLoaders = Arrays.stream(clazz.getAnnotations()) .filter(annotation -> annotation.annotationType().getAnnotation(PactSource.class) != null) .collect(toList()); if ((pactSource == null ? 0 : 1) + pactLoaders.size() != 1) { throw new InitializationError("Exactly one pact source should be set"); } try { if (pactSource != null) { final Class<? extends PactLoader> pactLoaderClass = pactSource.value(); try { // Checks if there is a constructor with one argument of type Class. final Constructor<? extends PactLoader> contructorWithClass = pactLoaderClass.getDeclaredConstructor(Class.class); contructorWithClass.setAccessible(true); return contructorWithClass.newInstance(clazz.getJavaClass()); } catch(NoSuchMethodException e) { LOGGER.error(e.getMessage(), e); return pactLoaderClass.newInstance(); } } else { final Annotation annotation = pactLoaders.iterator().next(); return annotation.annotationType().getAnnotation(PactSource.class).value() .getConstructor(annotation.annotationType()).newInstance(annotation); } } catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { LOGGER.error("Error while creating pact source", e); throw new InitializationError(e); } } }