Published on

Getting Pact Tests Running on the Provider in Java

Authors

In Pact there are various approaches you can use to verify consumer Pacts. The main thing that is consistent across approaches is that you need to wire in an implementation of the Target interface into the test. This interface has different implementations based on what your provider exposes as an endpoint. There are a number of different options like HTTP to test HTTP related providers, AMQP to test queues and the MockMVC version to test purely the REST portion of your contract. In my case I wanted to use Pact to test the provider boundary to ensure that changes to the provider do not result in any downstream processes breaking. I settled on the MockMvcTarget after running into numerous complications using the Spring based approach like having to workout how to get based the app's security in a testing context. I used code similar to the below:

import au.com.dius.pact.provider.junit.Provider;
import au.com.dius.pact.provider.junit.RestPactRunner;
import au.com.dius.pact.provider.junit.VerificationReports;
import au.com.dius.pact.provider.junit.loader.PactFolder;
import au.com.dius.pact.provider.junit.target.TestTarget;
import au.com.dius.pact.provider.spring.target.MockMvcTarget;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.time.Instant;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;

import static org.mockito.Mockito.doReturn;

@RunWith(RestPactRunner.class)
@Provider("my_service_provider")
@VerificationReports({"console", "markdown"})
@PactFolder("pacts")
public class MyRestControllerPactTest {

    /*this is the business service that you need to mock as part of this test
      we are testing the contract i.e. the shape of the response object more than what happens in between */
    @Mock
    private MyService myServiceMock;

    @InjectMocks
    private MyRestController myRestController;

    //this is a Pact object used to run MockMVC tests against the provider endpoint you are testing
    @TestTarget
    public final MockMvcTarget target = new MockMvcTarget();

    @Before
    public void setUp() {
        /*we need to initialize the Mockito mocks we defined with the @Mock annotation as we are not and cannot use the Mockito JUnit runner as we need to use the Pact JUnit runner */
        MockitoAnnotations.initMocks(this);
        target.setControllers(myRestController);
        //we use this to output the request and response when the test runs for debugging purposes
        target.setPrintRequestResponse(true);

        // this is where we mock out the business service used by the REST endpoint we are testing with this Pact
        SomeBusinessObject someBusinessObject = new SomeBusinessObject();
        doReturn(Optional.of(someBusinessObject)).when(myServiceMock).someBusinessMethod(Mockito.anyString());
    }
}

In the above you have to make your @TestTarget public in this case it is the public final MockMvcTarget target = new MockMvcTarget(); line, otherwise you will get the following error when trying to run this test:

java.lang.RuntimeException: How did getFields return a field we couldn't access?

	at org.junit.runners.model.TestClass.getAnnotatedFieldValues(TestClass.java:235)
	at au.com.dius.pact.provider.junit.InteractionRunner.lookupTarget(InteractionRunner.java:249)
	at au.com.dius.pact.provider.junit.InteractionRunner.interactionBlock(InteractionRunner.java:227)
	at au.com.dius.pact.provider.junit.InteractionRunner.run(InteractionRunner.java:168)
	at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:124)
	at au.com.dius.pact.provider.junit.PactRunner.runChild(PactRunner.kt:52)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.IllegalAccessException: Class org.junit.runners.model.FrameworkField can not access a member of class my.company.MyRestControllerPactTest with modifiers "private final"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.get(Field.java:390)
	at org.junit.runners.model.FrameworkField.get(FrameworkField.java:73)
	at org.junit.runners.model.TestClass.getAnnotatedFieldValues(TestClass.java:230)
	... 16 more