Is it possible to combine a csv feeder with a foreach to create a List of case classes and store them in Session?

I am trying to create a test that first loads a list of authenticated users into the Session.

Will something like this approach actually work?

iimport io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

object Authentication {

  case class Client(key: String, secret: String, accessToken: String)

  val authUrl = "https://api.example.com/oauth2/token"

  val authRequestHeaders = Map(
    "content-type" -> "application/x-www-form-urlencoded",
    "accept" -> """*/*""",
    "user-agent" -> """Gatling/3.6.1""",
    "accept-encoding" -> """gzip, deflate, br"""
  )

  val httpProtocol = http.baseUrl(authUrl)
    .headers(authRequestHeaders)

  val clientFeeder = csv("data/env/clients.csv").eager
  val clientList = List.empty[Client]

  def authenticate() = {
    foreach(clientFeeder.readRecords, "client") {
      exec { session =>
        http("auth")
          .post(authUrl)
          .formParamMap(Map(
            "client_id" -> "#{client.clientId}",
            "client_secret" -> "#{client.clientSecret}",
            "grant_type" -> "client_credentials"
          ))
          .check(jsonPath("$.access_token").saveAs("accessToken"))
        val client = Client("#{client.clientId}", "#{client.clientSecret}", "#{accessToken}")
        val newClientList = clientList :+ client
        val newSession = session.set("clientList", newClientList)
        session
      }
    }
  }
}

Example clients.csv file:

clientId,clientSecret
abc123, xyz=
def456, abc=
ghi789, def=

Lots of things wrong here.

val clientFeeder = csv(“data/env/clients.csv”).eager

Why do you force eager? Looks unnecessary to me.

val clientList = List.empty[Client]

This is an immutable reference to an immutable collection. There’s no way to update it. And indeed, you don’t as you’re creating a new List stored in the Session, not this reference.

You must either use a mutable reference (var) or a mutable collection (eg ArrayBuffer). Or switch to Java instead of struggling with Scala you’re not familiar with.

foreach(clientFeeder.readRecords, “client”)

Way more simple:

  • repeat(clientFeeder.recordsCount()) { , See Gatling - Feeders
  • then feed(clientFeeder) , this way you directly have access to the attributes with Gatling EL, without the client . prefix

exec { session =>
http(“auth”)

As explained in the documentation (Gatling - Scenario), you can’t use Gatling DSL inside functions. You must perform your HTTP request and THEN your function.

Client(“#{client.clientId}”

As explained in the documentation (Gatling - Expression Language), only Gatling DSL methods trigger Gatling Expression Language, not arbitrary code. You’re inside a function whose input parameter is the Session, you must use the Session API: Gatling - Session API

val newClientList = clientList :+ client

This is where you probably want to update a global reference, not create a new copy.

val newSession = session.set(“clientList”, newClientList)
session

As explained in the documentation, Session is immutable: Gatling - Session API. Here, you’re creating a copy with your new data, but merely discarding it as you’re returning the original version instead.
But as explained above, you probably want to upgrade the global reference instead.

clientId,clientSecret
abc123, xyz=

In CSV, whitespaces are significant, so the clientSecret is going to be xyz= with a heading whitespace.

1 Like

Thank you for the detailed response. :+1:

Remember that the Gatling DSL components are merely definitions. 
They have absolutely no effect when not chained with other DSL components. 
Typically, you can’t use them in functions.

"Typically" implies there are cases in which they can be used. Are there examples of when that may be the case?

That’s more of a curiosity though than the root of my question / confusion.

I think one of the things that is confusing me is this statement / requirement:

You must perform your HTTP request and THEN your function.

That seems backward to me for some reason.
I must be missing how you create a parameterized http request based on data from a csv for example if the http request happens first.

Wrong word indeed, replacing with “In particular”.

That seems backward to me for some reason. I must be missing how you create a parameterized http request based on data from a csv for example if the http request happens first.

No.

exec(
http("auth")
          .post(authUrl)
          .formParamMap(Map(
            "client_id" -> "#{client.clientId}",
            "client_secret" -> "#{client.clientSecret}",
            "grant_type" -> "client_credentials"
          ))
          .check(jsonPath("$.access_token").saveAs("accessToken"))
)
.exec { session =>
 // fetch session data, including accessToken that you only have now
// build a Client
// add it to your buffer
 session
}

I really do appreciate your patience and quick response times with my obvious confusion, so thank you very much

Let me back up and try and simplify quite a bit.

For the moment, I’m comfortable with making auth calls, even though a token will last for a period longer than the next call is likely to come in.

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

object Authentication {

  val authUrl = "https://api.example.com/oauth2/token"

  val authRequestHeaders = Map(
    "content-type" -> "application/x-www-form-urlencoded",
    "accept" -> """*/*""",
    "user-agent" -> """Gatling/3.6.1""",
    "accept-encoding" -> """gzip, deflate, br"""
  )

  val httpProtocol = http.baseUrl(authUrl)
    .headers(authRequestHeaders)


  def authenticate() =
      exec(
        http("auth")
          .post(authUrl)
          .formParamMap(Map(
            "client_id" -> "#{clientId}",
            "client_secret" -> "#{clientSecret}",
            "grant_type" -> "client_credentials"
          ))
          .check(jsonPath("$.access_token").saveAs("accessToken")))
}
object HelloWorld extends Simulation {

val apiUrl = "http://some.protected.resource/hello"

val apiRequestHeaders = Map (
  "content-type" -> "application/x-www.form-urlencoded",
  "accept" -> """"*/*""",
  "user-agent" -> "GatlingLoadTest",
  "accept-encoding" -> "gzip, deflat, br"
)

val apiHttpProtocol = http
  .baseUrl(apiUrl)
  .headers(apiRequestHeaders)

def helloWorld() = {
  exec(http("Say Hello")
  .get(apiUrl)
  .headers(apiRequestHeaders ++ Map( 
    "Authorization" -> "Bearer #{accessToken}"
    ))
    .check(status.is(200)))

clientId,clientSecret
abc123,xyz=
def456,abc=
ghi789,def=
class HelloWorld extends Simulation {

val clientFeeder = csv("data/clients.csv).random

val scn = scenario("Hello World with OAuth")
  .exec(authenticate())
  .exec(helloWorld())

setup(
  scn.inject(atOnceUsers(1))
  

Where I continue to struggle is how I get the Feeder to pass in the values so the authenticate()

I have tried: (based on the docs: Gatling - Feeders)

val scn = scenario("Hell World with OAuth")
  feed(clientFeeder)
  .exec(authenticate())
  .exec(helloWorld())

but, this causes the error:

Run crashed
java.lang.IllegalArgumentException: requirement failed: Scenario Hello World with OAuth is empty

Thanks again for your patience.

val scn = scenario("Hell World with OAuth")
  feed(clientFeeder) // <= MISSING DOT TO ATTACH WITH SCENARIO
  .exec(authenticate())
  .exec(helloWorld())

Well, that’s an embarrassingly easy fix.
Thank you. Things are behaving as expected now.
I appreciate all your help!