spring-bootwiremock

Wiremock failed on Spring Bean init method


I tried to mock three APIs provided by ShortPixel with Wiremock.

For the simplest read domain API, https://kb.shortpixel.com/knowledge-base/article/shortpixels-cdn-api-endpoints/#1-toc-title

To check the status of a domain and its quota, you should use the following.

https://no-cdn.shortpixel.ai/read-domain/example.com

The statuses can be

  • 2 = All OK
  • 1 = Credits almost exhausted
  • -1 = Credits exhausted
  • -2 = Credits used up some time ago (can be used e.g. to stop using CDN links)
  • -3 = Domain not reachable

I created the following client codes for it.

val domainResult = noCdnWebClient
    .get().uri(readDomainPath)
    .awaitExchange {
        if (it.statusCode().isError) {
            throw ImageOptimizerException("Failed to check domain $domain on shortpixel, ${it.awaitBody<String>()}")
        }
        it.awaitBody(Int::class)
    }

There is a little specificity here. I put the above codes into a @PostConstruct method to check the domain's existence. If the domain is not bound to the APIKEY, then call /add-domain to bind it.

When using the following stub to mock the request/response.

stubFor(
    get(urlPathEqualTo(readDomainPath))
        .willReturn(
            aResponse()
                .withStatus(200)
                .withBody("-3")
        )
)

When running the test I got the exception info like:

 HTTP GET http://localhost:9000/read-domain/localhost
 Response 404 NOT_FOUND
 Decoded "No response could be served as there are no stub mappings in this WireMock instance."

I am afraid the Wiremock server is not ready when the component is initialized.


Solution

  • The problem is when running the tests, the bean which init method called remote APIs is invoked before Wiremock is ready.

    The simplest approach is to adjust the testing codes slightly, make the bean not managed by the Spring application context in your tests, and create an instance manually.

    @SpringBootTest(classes=[MyTest.TestConfig.class])
    @Wiremock
    class MyTest{
    
    @Configuration
    @Import...// import dependencies (A, B)
    @ImportAutoConfiguration...
    class TestConfig() // do not contain the bean in the config
    
    @Autowired lateinit var depA:A
    @Autowired lateinit var depB:B
    
    // before running the tests, wiremock is ready now.
    
    // in the test, create the bean instance manually, called the init method and 
    // execute other tasks
    val bean = MyBean(depA, depB)
    bean.init()
    bean.doOtherThings()
    
    }