Hi everyone, how's it going? I'm trying to mock an...
# wiremock-java
m
Hi everyone, how's it going? I'm trying to mock an external service that my API consumes. I found WireMock to be a good solution, but I'm facing some difficulties.
Copy code
@Test
void shouldThrowExceptionWhenAuthorizationFails() throws Exception {
    Mockito.when(authorizationClient.execute())
            .thenReturn(new Authorize("fail", new Data(false)));

    var response = testScenario.executeTransferMoneyRequest(
            AMOUNT_125, 3, 38
    );

    assertThat(response.getStatus()).isEqualTo(403);

    Mockito.verify(authorizationClient, Mockito.times(1)).execute();
}

// I will use WireMock here.
public void paymentAllowedByAuthorizer(Boolean value) {
    if (Boolean.FALSE.equals(value)) {
        Mockito.when(authorizationClient.execute())
                .thenReturn(new Authorize("fail", new Data(value)));
    } else {
        Mockito.when(authorizationClient.execute())
                .thenReturn(new Authorize("success", new Data(value)));
    }
}
I'm running WireMock via
docker-compose
, along with my database.
Copy code
services:
  wiremock:
    image: "wiremock/wiremock:latest"
    container_name: wiremock-standalone
    entrypoint: [ "/docker-entrypoint.sh", "--global-response-templating", "--disable-gzip", "--verbose" ]
    ports:
      - "8080:8080"

  postgres:
    image: postgres:15
    container_name: simplifypay_postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: miguel
      POSTGRES_DB:
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  wiremock_data:
I defined a Bean for WireMock in my test configuration class.
Copy code
@TestConfiguration
public class TestConfig {
    @Bean
    public TransferMoneyTestScenario transferMoneyTestScenario() {
        return new TransferMoneyTestScenario();
    }

    @Bean
    public UserTestScenario userTestScenario() {
        return new UserTestScenario();
    }

    @Bean
    public WireMockServer wireMockServer() {
        return new WireMockServer(8080);
    }
}
My external server is a devtools instance, and it only returns two types of responses:
Copy code
{
  "status": "success",
  "data": {
    "authorization": true
  }
}
OR
Copy code
{
  "status": "fail",
  "data": {
    "authorization": false
  }
}
This is the class where I'll abstract the code to mock my external authorization server.
Copy code
@SpringBootTest
public class MockingAuth {

    @Autowired
    private WireMockServer wireMockServer;

    @Autowired
    private JacksonTester<Authorize> jacksonTester;

    @BeforeEach
    void init() {
        WireMock.configureFor("localhost", 8080);
        WireMock.startRecording("<<https://util.devi.tools/api/v2/authorize>>");
    }

    @Test
    void recordingExternalRequests() throws IOException {
        var response = new Authorize("success", new Data(true));

        var json = jacksonTester.write(response).getJson();

        WireMock.stubFor(post(urlPathEqualTo("/api/v2/authorize"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withBody(json)));

        System.out.println("Make external service calls here to record.");
    }

    @AfterEach
    void tearDown() {
        var toReturn = WireMock.stopRecording().getStubMappings();
        System.out.println("Recording finished. Stubs generated: " + toReturn.size());

        wireMockServer.stop();
    }
}
However, I'm getting this error when running the test:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'SimplifyPay.infrastructure.controllers.ClasseDeTeste': Unsatisfied dependency expressed through field 'wireMockServer': No qualifying bean of type 'com.github.tomakehurst.wiremock.WireMockServer' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)
l
It is difficult to see what is going on here. The error says the problem is with
ClasseDeTeste
class. Is that class in the code you added to your question ?
👀 1
m
@Lee Turner I wasn't expecting to get a reply so quickly, so I made some changes to try and solve the issue. I'll explain my API and code to make it clearer. Sorry if my initial question was confusing—it's my first time asking here. My API is a money transfer API, and it goes through several validations. One of these validations involves an external authorization system: https://util.devi.tools/api/v2/authorize. This is my implementation (I believe reading it isn't necessary to understand the issue):
Copy code
@Service
@RequiredArgsConstructor(onConstructor= @__(@Autowired))
public class TransferMoneyImpl implements TransferMoneyUseCase {
    // ... (Logger, Repositories, etc.)
    
    private final AuthorizationClient authClient; 

    @Override
    @Transactional
    public TransferMoneyResponse execute(TransferMoneyRequest req) {
        // ... (Validations)

        authClient.execute(); // Calling the external authorization service
        <http://logger.info|logger.info>("External service authorized");

        // ... (Rest of the transfer logic)
    }

    // ... (Other methods)
}
The
AuthorizationClient
implementation is:
Copy code
@FeignClient(
    value = "authorization-dev-tools",
    url="<<https://util.devi.tools/api/v2>>"
)
public interface AuthorizationClient {
    @GetMapping("/authorize")
    Authorize execute();
}
In my integration tests, I validate different use cases, and one of these tests checks how the API reacts when the external authorization denies the transfer. For this, I want to mock my
AuthorizationClient
, but I haven't succeeded in doing so yet. Currently, I'm trying to mock the
AuthorizationClient
using WireMock. I configured my
pom.xml
and
docker-compose
for this:
Copy code
<!-- WireMock for mocking external APIs -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <scope>test</scope>
</dependency>

services:
  wiremock:
    image: "wiremock/wiremock:latest"
    container_name: wiremock-standalone
    entrypoint: [ "/docker-entrypoint.sh", "--global-response-templating", "--disable-gzip", "--verbose" ]
    ports:
      - "8080:8080"
I added
WireMockServer
to my test configuration class as a Bean:
Copy code
@TestConfiguration
public class TestConfig {
    @Bean
    public WireMockServer wireMockServer() {
        return new WireMockServer(8080);
    }
}
I created an abstraction for the
AuthorizationClient
test:
Copy code
public class AuthorizationClientTest {

    @Autowired
    private static JacksonTester<Authorize> authorizeJacksonTester;

    public static void stubAuthorizationSuccess() throws Exception {
        var json = authorizeJacksonTester.write(new Authorize("success", new Data(true))).getJson();

        stubFor(get(urlEqualTo("/authorize"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody(json)));
    }

    public static void stubAuthorizationForbidden() throws Exception {
        var json = authorizeJacksonTester.write(new Authorize("fail", new Data(false))).getJson();

        stubFor(get(urlEqualTo("/authorize"))
                .willReturn(aResponse()
                        .withStatus(403)
                        .withHeader("Content-Type", "application/json")
                        .withBody(json)));
    }
}
And I implemented this abstraction in my test class:
Copy code
@SpringBootTest()
@AutoConfigureWireMock(port = 8080)
public class MockingAuth {

    @Autowired
    private AuthorizationClient authorizationClient;

    @Test
    public void shouldReturnSuccessResponse() throws Exception {
        stubAuthorizationSuccess();

        Authorize response = authorizationClient.execute();
        assertThat(response.status()).isEqualTo("success");
        assertThat(response.data().authorization()).isTrue();
    }

    @Test
    public void shouldReturnForbiddenResponse() throws Exception {
        stubAuthorizationForbidden();

        Authorize response = authorizationClient.execute();
        assertThat(response.status()).isEqualTo("fail");
        assertThat(response.data().authorization()).isFalse();
    }
}
The
docker-compose
setup works perfectly, and I can access WireMock routes on
localhost:8080
. However, when I run my test, I get the following error with a massive stack trace, and I haven't been able to figure out the solution >: the stacktrace summary
Copy code
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration...]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.cloud.contract.wiremock.WireMockConfiguration': Invocation of init method failed
Caused by: com.github.tomakehurst.wiremock.common.FatalStartupException: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:8080
Caused by: java.lang.RuntimeException: java.io.IOException: Failed to bind to /0.0.0.0:8080
Caused by: java.io.IOException: Failed to bind to /0.0.0.0:8080
Caused by: java.net.BindException: Address already in use: bind
My code is public; I'm developing this API purely for learning purposes. Feel free to take a look—I committed it to my develop branch to make the code easier to read. https://github.com/migueldelgg/simplify-pay/tree/develop
l
I don't use spring much so I am a little rusty here. Also, it is worth noting that I think
spring-cloud-starter-contract-stub-runner
uses a really old version of WireMock. We now have an official Spring integration that would probably be worth checking out given you are using the
3.x.x
release of Spring Boot. From looking at your error, WireMock is trying to connect to localhost:8080 but can't because something is already running on that port. Could it be that
spring-cloud-starter-contract-stub-runner
is trying to spin up a WireMock instance on localhost:8080 but you already have WireMock running in docker on that port ?
m
so let's try out, tks
👍 1
🙌 Thank You, @Lee Turner I successfully mocked the external API call by adding the library mentioned in the links you shared. 🚀 Here’s how my Stub turned out:
Copy code
@EnableWireMock({@ConfigureWireMock(name = "auth_mock", port = 9000)})
public class MockingAuth {

    @Value("${wiremock.server.baseUrl}")
    private String wireMockUrl;

    public static void stubAuthorizationSuccess() throws Exception {
        System.out.println("Stubbing /authorize for success");
        stubFor(get(urlEqualTo("/authorize"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\\\\"status\\\\":\\\\"success\\\\",\\\\"data\\\\":{\\\\"authorized\\\\":true}}")));
    }

    public static void stubAuthorizationForbidden() throws Exception {
        System.out.println("Stubbing /authorize for forbidden");
        stubFor(get(urlEqualTo("/authorize"))
                .willReturn(aResponse()
                        .withStatus(403)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\\\\"status\\\\":\\\\"fail\\\\",\\\\"data\\\\":{\\\\"authorized\\\\":false}}")));
    }
}
My
ControllerTest
Copy code
@SpringBootTest
@AutoConfigureJsonTesters
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("test")
@EnableWireMock({@ConfigureWireMock(name = "auth_mock", port = 9000)})
@Import(TestConfig.class)
class ControllerTest {

	// Other methods...

    @Test
    void shouldThrowExceptionWhenAuthorizationFails() throws Exception {
        // Given
        var commonUserId = userTestScenario.getIdFromResponse(commonUserResponse);
        var merchantUserId = userTestScenario.getIdFromResponse(merchantUserResponse);
        // Setup initial balances
        stubAuthorizationForbidden();

        // When
        var response = testScenario.executeTransferMoneyRequest(
                AMOUNT_100, commonUserId, merchantUserId
        );

        // Then
        assertThat(response.getStatus()).isEqualTo(403);
    }

    @Test
    void shouldTransferMoneyWhenAuthorizationWorks() throws Exception {
        // Given
        var commonUserId = userTestScenario.getIdFromResponse(commonUserResponse);
        var merchantUserId = userTestScenario.getIdFromResponse(merchantUserResponse);
        // Setup initial balances
        stubAuthorizationSuccess();

        // When
        var response = testScenario.executeTransferMoneyRequest(
                AMOUNT_100, commonUserId, merchantUserId
        );

        // Then
        assertThat(response.getStatus()).isEqualTo(200);
    }
    
    	// Other methods...

}
Feign Client I configured the
base_url
to be environment-specific, allowing for separate configurations for production and test environments:
Copy code
@FeignClient(
    value = "authorization-dev-tools", 
    url="${authorization-client.base-url}"
)
public interface AuthorizationClient {
    @GetMapping("/authorize")
    Authorize execute();
}
Production-Profile
Copy code
authorization-client.base-url=<https://util.devi.tools/api/v2>
Test-Profile
Copy code
authorization-client:
  base-url: <<http://localhost:9000>>
Once again, thank you for your quick responses and support! This made a huge difference in helping me overcome this challenge. 🙏
Oh, I forgot to mention, I removed WireMock in Docker-compose. I'm now running it directly through the library, starting the server, and so on.
l
Great stuff. Glad you got it all sorted.