Skip to content

Instantly share code, notes, and snippets.

@siwater
Created October 16, 2019 15:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save siwater/4144ce5d8aa1e5a157dd39af745a43bb to your computer and use it in GitHub Desktop.
Save siwater/4144ce5d8aa1e5a157dd39af745a43bb to your computer and use it in GitHub Desktop.
This code does not lose a Pact in PactRunner.kt
The attached file shows the change I made to filtering in PactRunner.kt (replace the call to filterPacts with one to filterPactsByAnnotations
at line 79)
Here is a sample log output:
2019-10-16 16:01:36.336 DEBUG 3464 --- [ main] a.c.d.p.p.junit.loader.PactBrokerLoader : Loading pacts from pact broker for provider cas-app-actions and tag latest
2019-10-16 16:01:36.369 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: /
2019-10-16 16:01:38.127 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: https://cas-pact-broker.eng.citrite.net/pacts/provider/cas-app-actions/latest
2019-10-16 16:01:38.205 DEBUG 3464 --- [ main] a.c.dius.pact.core.pactbroker.HalClient : Fetching: https://cas-pact-broker.eng.citrite.net/pacts/provider/cas-app-actions/consumer/sf-action-adapter/version/1.0.0
2019-10-16 16:01:38.370 DEBUG 3464 --- [ main] a.c.dius.pact.provider.junit.PactRunner : Loaded 1 pacts in java.util.ArrayList
Verifying a pact between sf-action-adapter and cas-app-actions
Given Received the message to trigger disable share action
Disable share action payload
package au.com.dius.pact.provider.junit
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver
import au.com.dius.pact.provider.junit.JUnitProviderTestSupport.filterPactsByAnnotations
import au.com.dius.pact.provider.junit.loader.NoPactsFoundException
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 com.google.gson.JsonSyntaxException
import mu.KLogging
import org.junit.Ignore
import org.junit.runner.notification.RunNotifier
import org.junit.runners.ParentRunner
import org.junit.runners.model.InitializationError
import org.junit.runners.model.TestClass
import java.io.IOException
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.findAnnotation
/**
* JUnit Runner runs pacts against provider
* To set up name of tested provider use [Provider] annotation
* To point on pact's source use [PactBroker], [PactFolder] or [PactSource] annotations
*
*
* To point provider for testing use combination of [Target] interface and [TestTarget] annotation
* There is out-of-the-box implementation of [Target]:
* [HttpTarget] that will play interaction from pacts as http request and check http responses
*
*
* Runner supports:
* - [org.junit.BeforeClass], [org.junit.AfterClass] and [org.junit.ClassRule] annotations,
* that will be run once - before/after whole contract test suite
*
*
* - [org.junit.Before], [org.junit.After] and [org.junit.Rule] annotations,
* that will be run before/after each test of interaction
* **WARNING:** please note, that only [org.junit.rules.TestRule] is possible to use with this runner,
* i.e. [org.junit.rules.MethodRule] **IS NOT supported**
*
*
* - [State] - before each interaction that require state change,
* all methods annotated by [State] with appropriate state listed will be invoked
*/
open class PactRunner<I>(clazz: Class<*>) : ParentRunner<InteractionRunner<I>>(clazz) where I : Interaction {
private val child = mutableListOf<InteractionRunner<I>>()
private var valueResolver = SystemPropertyResolver()
init {
if (clazz.getAnnotation(Ignore::class.java) != null) {
logger.info("Ignore annotation detected, exiting")
} else {
val providerInfo = clazz.getAnnotation(Provider::class.java) ?: throw InitializationError(
"Provider name should be specified by using ${Provider::class.java.name} annotation")
val serviceName = providerInfo.value
val consumerInfo = clazz.getAnnotation(Consumer::class.java)
val consumerName = consumerInfo?.value
val testClass = TestClass(clazz)
val ignoreNoPactsToVerify = clazz.getAnnotation(IgnoreNoPactsToVerify::class.java)
val ignoreIoErrors = try {
valueResolver.resolveValue(ignoreNoPactsToVerify?.ignoreIoErrors)
} catch (e: RuntimeException) {
logger.debug(e) { "Failed to resolve property value" }
ignoreNoPactsToVerify?.ignoreIoErrors
} ?: "false"
val pactLoader = getPactSource(testClass)
val pacts = try {
filterPactsByAnnotations(pactLoader.load(serviceName)
.filter { p -> consumerName == null || p.consumer.name == consumerName } as List<Pact<I>>,testClass.javaClass)
} catch (e: IOException) {
if (ignoreIoErrors == "true") {
logger.warn { "\n" + WARNING_ON_IGNORED_IOERROR.trimIndent() }
logger.debug(e) { "Failed to load pact files" }
emptyList<Pact<I>>()
} else {
throw InitializationError(e)
}
} catch (e: JsonSyntaxException) {
if (ignoreIoErrors == "true") {
logger.warn { "\n" + WARNING_ON_IGNORED_IOERROR.trimIndent() }
logger.debug(e) { "Failed to load pact files" }
emptyList<Pact<I>>()
} else {
throw InitializationError(e)
}
} catch (e: NoPactsFoundException) {
logger.debug(e) { "No pacts found" }
emptyList<Pact<I>>()
}
logger.debug("Loaded " + pacts.size + " pacts in ${pacts::class.qualifiedName}")
if (pacts.isEmpty()) {
if (ignoreNoPactsToVerify != null) {
logger.warn { "Did not find any pact files for provider ${providerInfo.value}" }
} else {
throw InitializationError("Did not find any pact files for provider ${providerInfo.value}")
}
}
setupInteractionRunners(testClass, pacts, pactLoader)
}
}
protected open fun setupInteractionRunners(testClass: TestClass, pacts: List<Pact<I>>, pactLoader: PactLoader) {
for (pact in pacts) {
this.child.add(newInteractionRunner(testClass, pact, pactLoader.pactSource))
}
}
protected open fun newInteractionRunner(
testClass: TestClass,
pact: Pact<I>,
pactSource: au.com.dius.pact.core.model.PactSource
): InteractionRunner<I> {
return InteractionRunner(testClass, pact, pactSource)
}
protected open fun filterPacts(pacts: List<Pact<I>>): List<Pact<I>> {
logger.debug("filterPacts input: ${pacts.size}")
val result = filterPactsByAnnotations(pacts, testClass.javaClass)
logger.debug("filterPacts output: ${result.size}")
return result
}
override fun getChildren() = child
override fun describeChild(child: InteractionRunner<I>) = child.description
override fun runChild(interaction: InteractionRunner<I>, notifier: RunNotifier) {
interaction.run(notifier)
}
protected open fun getPactSource(clazz: TestClass): PactLoader {
val pactSource = clazz.getAnnotation(PactSource::class.java)
val pactLoaders = clazz.annotations
.filter { annotation -> annotation.annotationClass.findAnnotation<PactSource>() != null }
if ((if (pactSource == null) 0 else 1) + pactLoaders.size != 1) {
throw InitializationError("Exactly one pact source should be set")
}
try {
if (pactSource != null) {
val pactLoaderClass = pactSource.value
return try {
// Checks if there is a constructor with one argument of type Class.
val constructorWithClass = pactLoaderClass.java.getDeclaredConstructor(Class::class.java)
if (constructorWithClass != null) {
constructorWithClass.isAccessible = true
constructorWithClass.newInstance(clazz.javaClass)
} else {
pactLoaderClass.createInstance()
}
} catch (e: NoSuchMethodException) {
logger.error(e) { e.message }
pactLoaderClass.createInstance()
}
} else {
val annotation = pactLoaders.first()
return annotation.annotationClass.findAnnotation<PactSource>()!!.value.java
.getConstructor(annotation.annotationClass.java).newInstance(annotation)
}
} catch (e: ReflectiveOperationException) {
logger.error(e) { "Error while creating pact source" }
throw InitializationError(e)
}
}
companion object : KLogging() {
const val WARNING_ON_IGNORED_IOERROR = """
---------------------------------------------------------------------------
| WARNING! Ignoring IO Exception received when loading Pact files as |
| WARNING! the @IgnoreNoPactsToVerify annotation is present and |
| WARNING! ignoreIoErrors is set to true. Make sure this is not happening |
| WARNING! on your CI server, as this could result in your build passing |
| WARNING! with no providers having been verified due to a configuration |
| WARNING! error. |
-------------------------------------------------------------------------"""
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment