-
-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for Spring 6 and Springboot 3 #1660
- Loading branch information
1 parent
6e1ff7f
commit bffd26a
Showing
29 changed files
with
1,605 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# Pact Spring6/Springboot3 + JUnit5 Support | ||
|
||
This module extends the base [Pact JUnit5 module](/provider/junit5/README.md) (See that for more details) and adds support | ||
for Spring 6 and Springboot 3. | ||
|
||
**NOTE: This module requires JDK 17+** | ||
|
||
## Dependency | ||
The combined library (JUnit5 + Spring6) is available on maven central using: | ||
|
||
group-id = au.com.dius.pact.provider | ||
artifact-id = spring6 | ||
version-id = 4.5.x | ||
|
||
## Usage | ||
For writing Spring Pact verification tests with JUnit 5, there is an JUnit 5 Invocation Context Provider that you can use with | ||
the `@TestTemplate` annotation. This will generate a test for each interaction found for the pact files for the provider. | ||
|
||
To use it, add the `@Provider` and `@ExtendWith(SpringExtension.class)` or `@SpringbootTest` and one of the pact source | ||
annotations to your test class (as per a JUnit 5 test), then add a method annotated with `@TestTemplate` and | ||
`@ExtendWith(PactVerificationSpring6Provider.class)` that takes a `PactVerificationContext` parameter. You will need to | ||
call `verifyInteraction()` on the context parameter in your test template method. | ||
|
||
For example: | ||
|
||
```java | ||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) | ||
@Provider("Animal Profile Service") | ||
@PactBroker | ||
public class ContractVerificationTest { | ||
|
||
@TestTemplate | ||
@ExtendWith(PactVerificationSpring6Provider.class) | ||
void pactVerificationTestTemplate(PactVerificationContext context) { | ||
context.verifyInteraction(); | ||
} | ||
|
||
} | ||
``` | ||
|
||
You will now be able to setup all the required properties using the Spring context, e.g. creating an application | ||
YAML file in the test resources: | ||
|
||
```yaml | ||
pactbroker: | ||
host: your.broker.host | ||
auth: | ||
username: broker-user | ||
password: broker.password | ||
``` | ||
You can also run pact tests against `MockMvc` without need to spin up the whole application context which takes time | ||
and often requires more additional setup (e.g. database). In order to run lightweight tests just use `@WebMvcTest` | ||
from Spring and `Spring6MockMvcTestTarget` as a test target before each test. | ||
|
||
For example: | ||
```java | ||
@WebMvcTest | ||
@Provider("myAwesomeService") | ||
@PactBroker | ||
class ContractVerificationTest { | ||
@Autowired | ||
private MockMvc mockMvc; | ||
@TestTemplate | ||
@ExtendWith(PactVerificationSpring6Provider.class) | ||
void pactVerificationTestTemplate(PactVerificationContext context) { | ||
context.verifyInteraction(); | ||
} | ||
@BeforeEach | ||
void before(PactVerificationContext context) { | ||
context.setTarget(new Spring6MockMvcTestTarget(mockMvc)); | ||
} | ||
} | ||
``` | ||
|
||
You can also use `Spring6MockMvcTestTarget` for tests without spring context by providing the controllers manually. | ||
|
||
For example: | ||
```java | ||
@Provider("myAwesomeService") | ||
@PactFolder("pacts") | ||
class MockMvcTestTargetStandaloneMockMvcTestJava { | ||
@TestTemplate | ||
@ExtendWith(PactVerificationSpring6Provider.class) | ||
void pactVerificationTestTemplate(PactVerificationContext context) { | ||
context.verifyInteraction(); | ||
} | ||
@BeforeEach | ||
void before(PactVerificationContext context) { | ||
Spring6MockMvcTestTarget testTarget = new Spring6MockMvcTestTarget(); | ||
testTarget.setControllers(new DataResource()); | ||
context.setTarget(testTarget); | ||
} | ||
@RestController | ||
static class DataResource { | ||
@GetMapping("/data") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
void getData(@RequestParam("ticketId") String ticketId) { | ||
} | ||
} | ||
} | ||
``` | ||
|
||
**Important:** Since `@WebMvcTest` starts only Spring MVC components you can't use `PactVerificationSpring6Provider` | ||
and need to fallback to `PactVerificationInvocationContextProvider` | ||
|
||
## Webflux tests | ||
|
||
You can test Webflux routing functions using the `WebFluxSpring6Target` target class. The easiest way to do it is to get Spring to | ||
autowire your handler and router into the test and then pass the routing function to the target. | ||
|
||
For example: | ||
|
||
```java | ||
@Autowired | ||
YourRouter router; | ||
@Autowired | ||
YourHandler handler; | ||
@BeforeEach | ||
void setup(PactVerificationContext context) { | ||
context.setTarget(new WebFluxSpring6Target(router.route(handler))); | ||
} | ||
@TestTemplate | ||
@ExtendWith(PactVerificationSpring6Provider.class) | ||
void pactVerificationTestTemplate(PactVerificationContext context) { | ||
context.verifyInteraction(); | ||
} | ||
``` | ||
|
||
## Modifying requests | ||
|
||
As documented in [Pact JUnit5 module](/provider/junit5/README.md#modifying-the-requests-before-they-are-sent), you can | ||
inject a request object to modify the requests made. However, depending on the Pact test target you are using, | ||
you need to use a different class. | ||
|
||
| Test Target | Class to use | | ||
|-----------------------------------------------|----------------------------------| | ||
| HttpTarget, HttpsTarget, SpringBootHttpTarget | org.apache.http.HttpRequest | | ||
| Spring6MockMvcTestTarget | MockHttpServletRequestBuilder | | ||
| WebFluxSpring6Target | WebTestClient.RequestHeadersSpec | | ||
|
||
# Verifying V4 Pact files that require plugins | ||
|
||
Pact files that require plugins can be verified with version 4.3.0+. For details on how plugins work, see the | ||
[Pact plugin project](https://github.com/pact-foundation/pact-plugins). | ||
|
||
Each required plugin is defined in the `plugins` section in the Pact metadata in the Pact file. The plugins will be | ||
loaded from the plugin directory. By default, this is `~/.pact/plugins` or the value of the `PACT_PLUGIN_DIR` environment | ||
variable. Each plugin required by the Pact file must be installed there. You will need to follow the installation | ||
instructions for each plugin, but the default is to unpack the plugin into a sub-directory `<plugin-name>-<plugin-version>` | ||
(i.e., for the Protobuf plugin 0.0.0 it will be `protobuf-0.0.0`). The plugin manifest file must be present for the | ||
plugin to be able to be loaded. | ||
|
||
# Test Analytics | ||
|
||
We are tracking anonymous analytics to gather important usage statistics like JVM version | ||
and operating system. To disable tracking, set the 'pact_do_not_track' system property or environment | ||
variable to 'true'. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
plugins { | ||
id 'au.com.dius.pact.kotlin-library-conventions' | ||
} | ||
|
||
description = 'Provider Spring6/Springboot3 + JUnit5 Support' | ||
group = 'au.com.dius.pact.provider' | ||
|
||
java { | ||
targetCompatibility = '17' | ||
sourceCompatibility = '17' | ||
} | ||
|
||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { | ||
kotlinOptions { | ||
jvmTarget = "17" | ||
} | ||
} | ||
|
||
dependencies { | ||
api project(':provider:junit5') | ||
|
||
implementation 'org.springframework:spring-context:6.0.4' | ||
implementation 'org.springframework:spring-test:6.0.4' | ||
implementation 'org.springframework:spring-web:6.0.4' | ||
implementation 'org.springframework:spring-webflux:6.0.4' | ||
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' | ||
implementation 'org.hamcrest:hamcrest:2.2' | ||
implementation 'org.apache.commons:commons-lang3' | ||
implementation 'javax.mail:mail:1.5.0-b01' | ||
|
||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.0.2' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-web:3.0.2' | ||
testImplementation 'org.apache.groovy:groovy' | ||
testImplementation 'org.mockito:mockito-core:4.8.1' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Pact-JVM - Provider Spring6/Springboot3 + JUnit5 Support |
41 changes: 41 additions & 0 deletions
41
.../main/kotlin/au/com/dius/pact/provider/spring/spring6/PactVerificationSpring6Extension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package au.com.dius.pact.provider.spring.spring6 | ||
|
||
import au.com.dius.pact.core.model.Interaction | ||
import au.com.dius.pact.core.model.Pact | ||
import au.com.dius.pact.core.model.PactSource | ||
import au.com.dius.pact.provider.junit5.PactVerificationContext | ||
import au.com.dius.pact.provider.junit5.PactVerificationExtension | ||
import org.junit.jupiter.api.extension.ExtensionContext | ||
import org.junit.jupiter.api.extension.ParameterContext | ||
import org.springframework.test.web.reactive.server.WebTestClient | ||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder | ||
|
||
open class PactVerificationSpring6Extension( | ||
pact: Pact, | ||
pactSource: PactSource, | ||
interaction: Interaction, | ||
serviceName: String, | ||
consumerName: String? | ||
) : PactVerificationExtension(pact, pactSource, interaction, serviceName, consumerName) { | ||
constructor(context: PactVerificationExtension) : this(context.pact, context.pactSource, context.interaction, | ||
context.serviceName, context.consumerName) | ||
|
||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { | ||
val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm")) | ||
val testContext = store.get("interactionContext") as PactVerificationContext | ||
return when (parameterContext.parameter.type) { | ||
MockHttpServletRequestBuilder::class.java -> testContext.target is Spring6MockMvcTestTarget | ||
WebTestClient.RequestHeadersSpec::class.java -> testContext.target is WebFluxSpring6Target | ||
else -> super.supportsParameter(parameterContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any? { | ||
val store = extensionContext.getStore(ExtensionContext.Namespace.create("pact-jvm")) | ||
return when (parameterContext.parameter.type) { | ||
MockHttpServletRequestBuilder::class.java -> store.get("request") | ||
WebTestClient.RequestHeadersSpec::class.java -> store.get("request") | ||
else -> super.resolveParameter(parameterContext, extensionContext) | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...c/main/kotlin/au/com/dius/pact/provider/spring/spring6/PactVerificationSpring6Provider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package au.com.dius.pact.provider.spring.spring6 | ||
|
||
import au.com.dius.pact.core.support.expressions.ValueResolver | ||
import au.com.dius.pact.provider.junit5.PactVerificationExtension | ||
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider | ||
import org.junit.jupiter.api.extension.ExtensionContext | ||
import org.junit.jupiter.api.extension.TestTemplateInvocationContext | ||
import org.springframework.test.context.TestContextManager | ||
import org.springframework.test.context.junit.jupiter.SpringExtension | ||
import java.util.stream.Stream | ||
|
||
open class PactVerificationSpring6Provider : PactVerificationInvocationContextProvider() { | ||
|
||
override fun getValueResolver(context: ExtensionContext): ValueResolver? { | ||
val store = context.root.getStore(ExtensionContext.Namespace.create(SpringExtension::class.java)) | ||
val testClass = context.requiredTestClass | ||
val testContextManager = store.getOrComputeIfAbsent(testClass, { TestContextManager(testClass) }, | ||
TestContextManager::class.java) | ||
val environment = testContextManager.testContext.applicationContext.environment | ||
return Spring6EnvironmentResolver(environment) | ||
} | ||
|
||
override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream<TestTemplateInvocationContext> { | ||
return super.provideTestTemplateInvocationContexts(context).map { | ||
if (it is PactVerificationExtension) { | ||
PactVerificationSpring6Extension(it) | ||
} else { | ||
it | ||
} | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...g6/src/main/kotlin/au/com/dius/pact/provider/spring/spring6/Spring6EnvironmentResolver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package au.com.dius.pact.provider.spring.spring6 | ||
|
||
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver | ||
import au.com.dius.pact.core.support.expressions.ValueResolver | ||
import org.springframework.core.env.Environment | ||
|
||
class Spring6EnvironmentResolver(private val environment: Environment) : ValueResolver { | ||
override fun resolveValue(property: String?): String? { | ||
val tuple = SystemPropertyResolver.PropertyValueTuple(property).invoke() | ||
return environment.getProperty(tuple.propertyName, tuple.defaultValue) | ||
} | ||
|
||
override fun resolveValue(property: String?, default: String?): String? { | ||
return environment.getProperty(property, default) | ||
} | ||
|
||
override fun propertyDefined(property: String) = environment.containsProperty(property) | ||
} |
Oops, something went wrong.