0
votes

I have multiple Gatling simulations that I am trying to structure in a way to reuse low-level calls (i.e. HTTP calls) while building different scenarios. For that reason I have the following structure (echo function is used for the sake of explaining the current state):

// File UserAction.scala contains all low-level actions like load a specific page, login, logout etc. All functions return HttpRequestBuilder so that each simulation can perform its own set of checks on the responses.
object UserAction {
    ...
    def echo(): HttpRequestBuilder = {
        http("Postman Echo Service Call")
            .get("https://postman-echo.com/get")
            .headers(headers("headers_0"))
    }
    ...
}

// File TestSimulation.scala
class TestSimulation extends ParameterizedSimulation {
    private val accountFeeder = csv("data/accounts.csv")

    private val testScenario = scenario("Test Simulation")
        .feed(accountFeeder)
        .exec { session =>
            session.set("attributes", session.attributes.filterKeys(key => key.startsWith("some_prefix_")))
        }
        .doIfOrElse(session => session("schema_attributes").as[Map[String, String]].isEmpty) {
            exec(
                echo()
                    .check(bodyString.saveAs("responseBody"))
            )
        } {
            exec(
                echo()
                    .formParamMap("${schema_attributes}")
                    .check(bodyString.saveAs("responseBody"))
            )
        }
        .exec { session => println(session("responseBody").as[String]); session }
    ...
}

In the test simulation provided above we feed data from a file and select a subset of attributes which we store as a Map into the Session. If such attributes are found in the file we add them as form parameters when submiting the echo request. Otherwise we don't. In both cases we store the response body and print it our as well. This works fine.

When I try to pass the Map as a parameter to echo() is when the issues begin. Suppose I have the following implementation of echo:

def echo(extraParameters: Map[String, Any]): HttpRequestBuilder = {
    val request = http("Postman Echo Service Call")
        .get("https://postman-echo.com/get")
        .headers(headers("headers_0"))

    if (extraParameters.nonEmpty) {
        request.formParamMap(extraParameters)
    }

    request
}

and that the simulation does the following:

.exec { session =>
            session.set("schema_attributes", session.attributes.filterKeys(key => key.startsWith("some_prefix_")))
}
.exec { session =>
    echo(session.attributes.filterKeys(key => key.startsWith("some_prefix_")))
        .check(bodyString.saveAs("responseBody"))
    session
}
.exec { session => println(session("responseBody").as[String]); session }

The requests don't fire and I get the following error about the bodyString missing

[ERROR] i.g.c.a.b.SessionHookBuilder$$anon$1 - 'hook-1' crashed with 'j.u.NoSuchElementException: No attribute named 'responseBody' is defined', forwarding to the next one

An alternative I tried was to make echo accept an Expression[Map[String, Any]] and use a normal exec block (i.e. not the one with the injected session). In that case I couldn't get the actual map out of the session!

It seems that the bodyString is only available when using the exec() and not the exec {session => ...; session} form. My Scala knowledge is very little which adds to my confusion.

I feel like I am missing something very fundamental about this. Any help will be greately appreciated.

UPDATE #1

Based on the comments received so far, as I understand it using the exec(sessionFunction: Expression[Session]) function creates the ActionBuilders during runtime so they are not used (they are just created and discarded).

In an attempt to keep all checks inside the scenario while keeping the definition of the HTTP requests on a different function I tried the following alternative:

def echo(): HttpRequestBuilder = {
    http("Postman Echo Service Call")
        .get("https://postman-echo.com/get")
        .headers(headers("headers_0"))
        .formParamMap("${schema_attributes}")
}

// Used as
.feed(myFeeder)
.exec { session => session.set("schemaAttributes", session.attributes.filterKeys(key => key.startsWith("some_prefix_")))
}
.exec(
    echo()
        .check(bodyString.saveAs("responseBody"))
)

This way I have the problem that if the map is empty (i.e. the information are not present in the file) formParamMap won't work. Is there a way to optionally add formParamMap if the Map actually contains data?

In general, is there a preferred pattern/method to pass parameters to functions that return HttpRequestBuilders from within a scenario? For example, if I have another function that takes an Int as a parameter and creates an HTTP request with the number in the request to be sent, how would I go about doing this?

1
gatling.io/docs/current/general/scenario See the warning about immutable ActionBuilders. - George Leung
Hello George! Thanks for the quick response. Checking the passage you referred to I understand that http, get and all DSL functions of Gatling are immutable builders and the don't execute any particular action unless used inside an exec block. The exec { session => ...; session } version is not one of those. Only the exec(actionBuilder: ActionBuilder) function will actually execute actions defined by my builders. Did I get this right? :) Thanks again for your help. - George E. Kallergis

1 Answers

1
votes

In your examples, echo is returning a HttpRequestBuilder. The way gatling works, builders should all get constructed at startup and then used to make requests, so when you include this in a session function the Builder gets created during execution, but then is never actually executed and the unchanged session is returned.

To make this work, you're going to have to move echo out of the session function. As you've realised, you're probably going to have to change the method signature to accept some kind of Expression