Arranging HTTP Request Testing in Spring – DZone – Uplaza

On this article, I want to describe an strategy to writing exams with a transparent division into separate phases, every performing its particular function. This facilitates the creation of exams which can be simpler to learn, perceive, and preserve.

The dialogue will deal with utilizing the Organize-Act-Assert methodology for integration testing within the Spring Framework with mocking of HTTP requests to exterior assets encountered in the course of the execution of the examined code inside the system habits. The exams into consideration are written utilizing the Spock Framework within the Groovy language. MockRestServiceServer can be used because the mocking mechanism. There will even be a couple of phrases about WireMock.

Drawback Description

When finding out the right way to write integration exams for Spring, I usually referred to supplies on the subject. Examples for MockRestServiceServer principally described an strategy with the declaration of expectations as follows:

  • Anticipated URI
  • Variety of requests to the anticipated URI
  • Expectations for the construction and content material of the request physique
  • Response to the request

The code seemed one thing like this:

@Check
public void testWeatherRequest() {
    mockServer.count on(as soon as(), requestTo("https://external-weather-api.com/forecast"))         
            .andExpect(technique(HttpMethod.POST))
            .andExpect(jsonPath("$.field1", equalTo("value1")))
            .andExpect(jsonPath("$.field2", equalTo("value2")))
            .andExpect(jsonPath("$.field3", equalTo("value3")))
            .andRespond(withSuccess('{"result": "42"}', MediaType.APPLICATION_JSON));
    weatherService.getForecast("London")
    mockServer.confirm()
    assert ..
    assert ..
}

When making use of this strategy, I encountered a lot of difficulties:

  1. Ambiguity in figuring out the explanations for AssertionErrorby the log textual content – the log textual content is similar for various situations:
    • The HTTP name code is lacking/not executed in line with enterprise logic.
    • The HTTP name code is executed with an error.
    • The HTTP name code is executed appropriately, however there’s an error within the mock description.
  2. Issue in figuring out the scope of the examined states because of their dispersion all through the take a look at code. Formally, the end result verification is carried out on the finish of the take a look at (mockServer.confirm()), however the verification assertions concerning the composition and construction of the request are described in the beginning of the take a look at (as a part of creating the mock). On the similar time, verification assertions not associated to the mock have been offered on the finish of the take a look at.
    • Necessary clarification: Utilizing RequestMatcher for the aim of isolating mocks inside many requests looks like the fitting resolution.

Proposed Resolution

Clear division of take a look at code into separate phases, in line with the Organize-Act-Assert sample.

Organize-Act-Assert

Organize-Act-Assert is a extensively used sample in writing exams, particularly in unit testing. Let’s take a more in-depth take a look at every of those steps:

Organize (Preparation)

At this stage, you arrange the take a look at setting. This contains initializing objects, creating mocks, organising obligatory knowledge, and many others. The objective of this step is to arrange every little thing wanted for the execution of the motion being examined.

Act (Execution)

Right here you carry out the motion you need to take a look at. This may very well be a technique name or a collection of actions resulting in a sure state or end result to be examined.

Assert (End result Verification)

On the last stage, you examine the outcomes of the motion. This contains assertions concerning the state of objects, returned values, modifications within the database, messages despatched, and many others. The objective of this step is to make sure that the examined motion has produced the anticipated end result.

Demonstration Situations

The enterprise logic of the service for which the exams can be supplied will be described as follows:

given: The climate service offers data that the climate in metropolis A equals B
when: We request climate knowledge from the service for metropolis A
then: We obtain B

Sequence Diagram

Instance Implementation for MockRestServiceServer Earlier than Proposed Adjustments

Checks for the above state of affairs can be described utilizing MockRestServiceServer.

Issue in Figuring out the Scope of Examined States On account of Their Dispersion All through the Check Code

def "Forecast for provided city London is 42"() {
    setup:          // (1)
    mockServer.count on(as soon as(), requestTo("https://external-weather-api.com/forecast")) // (2)
            .andExpect(technique(HttpMethod.POST))
            .andExpect(jsonPath('$.metropolis', Matchers.equalTo("London")))                // (3)
            .andRespond(withSuccess('{"result": "42"}', MediaType.APPLICATION_JSON)); // (4)
    when:          // (5)
    def forecast = weatherService.getForecast("London")
    then:          // (6)
    forecast == "42"     // (7)
    mockServer.confirm()  // (8)
}
  1. Setup stage: describing the mock
  2. Indicating that precisely one name is predicted to https://external-weather-api.com
  3. Specifying anticipated request parameters
  4. Describing the response to return
  5. Execution stage, the place the principle name to get the climate for the required metropolis happens
  6. Verification stage: Right here, mockServer.confirm() can be referred to as to examine the request (see merchandise 3).
  7. Verification assertion concerning the returned worth
  8. Calling to confirm the mock’s state

Right here we are able to observe the issue described earlier as “difficulty in determining the scope of tested states due to their dispersion throughout the test code” – a number of the verification assertions are within the then block, some within the setup block.

Ambiguity in Figuring out the Causes of AssertionError

To reveal the issue, let’s mannequin completely different error situations within the code. Beneath are the conditions and corresponding error logs.

  • Situation 1 – Handed an unknown metropolis title: def forecast = weatherService.getForecast("Unknown")
java.lang.AssertionError: No additional requests anticipated: HTTP POST https://external-weather-api.com
0 request(s) executed.

	at org.springframework.take a look at.net.shopper.AbstractRequestExpectationManager.createUnexpectedRequestError(AbstractRequestExpectationManager.java:193)
  • Situation 2: Incorrect URI declaration for the mock; for instance, mockServer.count on(as soon as(), requestTo("https://foo.com"))
java.lang.AssertionError: No additional requests anticipated: HTTP POST https://external-weather-api.com
0 request(s) executed.

	at org.springframework.take a look at.net.shopper.AbstractRequestExpectationManager.createUnexpectedRequestError(AbstractRequestExpectationManager.java:193)
  • Situation 3: No HTTP calls within the code
java.lang.AssertionError: Additional request(s) anticipated leaving 1 unhappy expectation(s).
0 request(s) executed.

The primary commentary: All errors are related, and the stack hint is kind of the identical.

Instance Implementation for MockRestServiceServer With Proposed Adjustments

Ease of Figuring out the Scope of Examined States On account of Their Dispersion All through the Check Code

def "Forecast for provided city London is 42"() {
    setup:          // (1)
    def requestCaptor = new RequestCaptor()
    mockServer.count on(manyTimes(), requestTo("https://external-weather-api.com"))          // (2)
            .andExpect(technique(HttpMethod.POST))
            .andExpect(requestCaptor)                                                      // (3)
            .andRespond(withSuccess('{"result": "42"}', MediaType.APPLICATION_JSON));      // (4)
    when:          // (5)
    def forecast = weatherService.getForecast("London")
    then:          // (6)
    forecast == "42"
    requestCaptor.instances == 1              // (7)
    requestCaptor.entity.metropolis == "London" // (8)
    requestCaptor.headers.get("Content-Type") == ["application/json"]
}
  • #3: Knowledge seize object
  • #7: Verification assertion concerning the variety of calls to the URI
  • #8: Verification assertion concerning the composition of the request to the URI

On this implementation, we are able to see that each one the verification assertions are within the then block.

Unambiguity in Figuring out the Causes of AssertionError

To reveal the issue, let’s try and mannequin completely different error situations within the code. Beneath are the conditions and corresponding error logs.

  • Situation 1: An unknown metropolis title was supplied def forecast = weatherService.getForecast("Unknown")
requestCaptor.entity.metropolis == "London"
|             |      |    |
|             |      |    false
|             |      |    5 variations (28% similarity)
|             |      |    (Unk)n(-)o(w)n
|             |      |    (Lo-)n(d)o(-)n
|             |      Unknown
|             [city:Unknown]
  • Scenario 2: Incorrect URI declaration for the mock; for example, mockServer.expect(once(), requestTo("https://foo.com"))
java.lang.AssertionError: No further requests expected: HTTP POST https://external-weather-api.com
0 request(s) executed.
  • Scenario 3: No HTTP calls in the code
Situation not glad:

requestCaptor.instances == 1
|             |     |
|             0     false

Utilizing WireMock

WireMock offers the power to explain verifiable expressions within the Assert block.

def "Forecast for provided city London is 42"() {
    setup:          // (1)
    wireMockServer.stubFor(publish(urlEqualTo("/forecast"))                              // (2)
            .willReturn(aResponse()                                                   // (4)
                    .withBody('{"result": "42"}')
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")))
    when:          // (5)
    def forecast = weatherService.getForecast("London")
    then:          // (6)
    forecast == "42"
    wireMockServer.confirm(postRequestedFor(urlEqualTo("/forecast"))
            .withRequestBody(matchingJsonPath('$.metropolis', equalTo("London"))))          // (7)
}

The above strategy may also be used right here, by describing the WiredRequestCaptor class.

def "Forecast for provided city London is 42"() {
    setup:
    StubMapping forecastMapping = wireMockServer.stubFor(publish(urlEqualTo("/forecast"))
            .willReturn(aResponse()
                    .withBody('{"result": "42"}')
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")))
    def requestCaptor = new WiredRequestCaptor(wireMockServer, forecastMapping)
    when:
    def forecast = weatherService.getForecast("London")
    then:
    forecast == "42"
    requestCaptor.instances == 1
    requestCaptor.physique.metropolis == "London"
}

This enables us to simplify expressions and improve the idiomaticity of the code, making the exams extra readable and simpler to keep up.

Conclusion

All through this text, I’ve dissected the phases of testing HTTP requests in Spring, utilizing the Organize-Act-Assert methodology and mocking instruments akin to MockRestServiceServer and WireMock. The first objective was to reveal how clearly dividing the take a look at into separate phases considerably enhances readability, understanding, and maintainability.

I highlighted the issues related to the anomaly of error dedication and the problem of defining the scope of examined states and offered methods to unravel them via a extra structured strategy to check writing. This strategy is especially vital in advanced integration exams, the place each facet is important to making sure the accuracy and reliability of the system.

Moreover, I confirmed how the usage of instruments like RequestCaptor and WiredRequestCaptor simplifies the test-writing course of and improves their idiomaticity and readability, thereby facilitating simpler help and modification.

In conclusion, I need to emphasize that the selection of testing strategy and corresponding instruments ought to be based mostly on particular duties and the context of growth. The strategy to testing HTTP requests in Spring offered on this article is meant to help builders dealing with related challenges.

The hyperlink to the challenge repository with demonstration exams will be discovered right here.

Thanks on your consideration to the article, and good luck in your pursuit of writing efficient and dependable exams!

Share This Article
Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Exit mobile version