Wick Technology Blog

Upgrading to use JUnit 5 for Spring Pact tests

April 12, 2019

When using the Spring Pact provider library provided by DiUS, verifying pacts that your Spring applications provide is super easy. The tests themselves barely have any code in them - in fact they look like they don’t contain any tests!

However the library uses the JUnit4 @RunWith annotation which is no longer supported with Junit5, so when I needed to upgrade JUnit I had to find a way to port our pact tests across.

It turns out it’s very easy and, despite requiring more lines of code, it’s more explicit and more extensible.

Imagine a provider UniqueIdProvider.

The tests started out as:

@Provider("UniqueIdProvider")
@PactBroker(tags = {"master", "development", "production"})
@VerificationReports
@RunWith(SpringRestPactRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ContractTest {

    @TestTarget
    public final Target target = new SpringBootHttpTarget();
    
    @MockBean
    private UniqueIdService uniqueIdService;

    @State("Unique ID service returns 1234")
    public void uniqueId() {
        given(uniqueIdService.getUniqueId()).willReturn("1234");
    }
}

And after adding testCompile('au.com.dius:pact-jvm-provider-junit5_2.12:3.6.3') to gradle (along with the required JUnit dependencies) the test became:

@Provider("UniqueIdProvider")
@PactBroker(tags = {"master", "development", "production"}, host = "pact-broker.tools", port = "443")
@VerificationReports
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ContractTest {

    @LocalServerPort
    int localServerPort;
    
    @MockBean
    private UniqueIdService uniqueIdService;
    
    @BeforeEach
    void setUp(PactVerificationContext context) {
         context.setTarget(new HttpTestTarget("localhost", localServerPort, "/"));
    }

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void testTemplate(PactVerificationContext context) {
        context.verifyInteraction();
    }

    @State("Unique ID service returns 1234")
    public void uniqueId() {
        given(uniqueIdService.getUniqueId()).willReturn("1234");
    }
}

The changes here are that @RunWith(SpringRunner.class) is replaced with @ExtendWith(SpringExtension.class) to run the test in a Spring context. This will set up the controllers and services which the pact verification will run against.

Previously SpringBootHttpTarget set up the target from the random port automatically but now we need to set the PactVerificationContext’s target for each test in a @BeforeEach annotated method.

One of the things I like is that using JUnit5’s TestTemplate means what is being tested is now explicit. Before, it was implicit and hidden with the JUnit4 method - notice how there was no @Test method in the JUnit4 test class.

The other thing I like is that this format is extensible - other extensions can be added to the class and the test is just running the Spring context using the standard extension. This means less things to learn and all standard Spring configuration and test practices will simply work.

Lastly the broker host and port need to be passed explicitly to the @PactBroker annotation since it can’t be inferred from the application properties anymore (this is something the SpringRestPactRunner used to do).

The migration was much simpler than I expected: just a combination of standard Spring test annotations and JUnit5 Pact additions.


Phil Hardwick

Written by Phil Hardwick