Arranging HTTP Request Testing in Spring – DZone – Uplaza

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

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 throughout the execution of the examined code throughout the system conduct. The checks into account are written utilizing the Spock Framework within the Groovy language. MockRestServiceServer might be used because the mocking mechanism. There can even be a number of phrases about WireMock.

Drawback Description

When finding out find out how to write integration checks for Spring, I typically referred to supplies on the subject. Examples for MockRestServiceServer principally described an method 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 regarded one thing like this:

@Take a look at
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 method, I encountered numerous difficulties:

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

Proposed Resolution

Clear division of check code into separate phases, in response to the Organize-Act-Assert sample.

Organize-Act-Assert

Organize-Act-Assert is a broadly used sample in writing checks, particularly in unit testing. Let’s take a better take a look at every of those steps:

Organize (Preparation)

At this stage, you arrange the check surroundings. This contains initializing objects, creating mocks, organising vital knowledge, and so forth. The purpose of this step is to organize all the pieces wanted for the execution of the motion being examined.

Act (Execution)

Right here you carry out the motion you need to check. This might be a technique name or a sequence of actions resulting in a sure state or end result to be examined.

Assert (Outcome Verification)

On the closing stage, you verify the outcomes of the motion. This contains assertions in regards to the state of objects, returned values, adjustments within the database, messages despatched, and so forth. The purpose 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 checks might be supplied may be described as follows:

given: The climate service gives 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 Modifications

Assessments for the above state of affairs might be described utilizing MockRestServiceServer.

Problem in Figuring out the Scope of Examined States Because of Their Dispersion All through the Take a look at 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 anticipated 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 verify the request (see merchandise 3).
  7. Verification assertion relating to the returned worth
  8. Calling to confirm the mock’s state

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

Ambiguity in Figuring out the Causes of AssertionError

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

  • Situation 1 – Handed an unknown metropolis identify: 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.check.internet.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.check.internet.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 principle statement: All errors are comparable, and the stack hint is kind of the identical.

Instance Implementation for MockRestServiceServer With Proposed Modifications

Ease of Figuring out the Scope of Examined States Because of Their Dispersion All through the Take a look at 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.occasions == 1              // (7)
    requestCaptor.entity.metropolis == "London" // (8)
    requestCaptor.headers.get("Content-Type") == ["application/json"]
}
  • #3: Knowledge seize object
  • #7: Verification assertion relating to the variety of calls to the URI
  • #8: Verification assertion relating to the composition of the request to the URI

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

Unambiguity in Figuring out the Causes of AssertionError

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

  • Situation 1: An unknown metropolis identify 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.occasions == 1
|             |     |
|             0     false

Utilizing WireMock

WireMock gives the flexibility 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 method can 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.occasions == 1
    requestCaptor.physique.metropolis == "London"
}

This enables us to simplify expressions and improve the idiomaticity of the code, making the checks extra readable and simpler to take care of.

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 similar to MockRestServiceServer and WireMock. The first purpose was to exhibit how clearly dividing the check into separate phases considerably enhances readability, understanding, and maintainability.

I highlighted the issues related to the paradox of error willpower and the problem of defining the scope of examined states and offered methods to resolve them by a extra structured method to check writing. This method is especially necessary in advanced integration checks, the place each side is vital to making sure the accuracy and reliability of the system.

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

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

The hyperlink to the challenge repository with demonstration checks may be discovered right here.

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

Share This Article
Leave a comment

Leave a Reply

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

Exit mobile version