1. Introduction

In this quick tutorial, we’ll create a test case that uses most of the BeanShell features available in JMeter. Ultimately, we’ll learn how to use the necessary tools to make any test with BeanShell scripts.

2. Setting up JMeter and BeanShell

To start, we’ll need to download JMeter. To run it, we need to extract the download anywhere and then run the executable (jmeter.sh for Unix-based systems and jmeter.bat for Windows).

At the time of writing, the latest stable version is 5.6.3, which already bundles BeanShell 2.0b6 binaries. Version 3 is currently in development and will support newer Java features. Until then, we’re stuck using Java 4 syntax, though JMeter runs with recent JVMs. We’ll see how this affects our scripts later on.

Most importantly, while BeanShell is a feature-rich scripting language, it’s primarily used to implement test steps in JMeter. We’ll see this in practice with our scenario: a POST request to an API while capturing statistical data, like total bytes sent and elapsed time.

After starting JMeter, the only non-BeanShell element we’ll need is the Thread Group. We create one by right-clicking “Test Plan” and then choosing “Add,” then “Threads (Users),” and then “Thread Group.” We only need to specify the number of threads and loops we want:

JMeter thread group

With that done, we’re ready to create the BeanShell-based elements for our test.

3. Pre Processor

We’ll start with a Pre Processor to configure values for our POST request. We do this by right-clicking our Thread Group, then clicking “Add”, then “Pre Processors”, then “BeanShell PreProcessor”. In its content, let’s write a script that’ll generate random values for the request:

random = new Random();

key = "k"+random.nextInt();
value = random.nextInt();

To declare variables, we don’t need to include their types explicitly, and we can use any types available in the JVM or JMeter’s lib folder.

Let’s use the vars object (a special variable shared between scripts) to save these values for later use, along with our API address:

vars.put("base-api", "http://localhost:8080/api");

vars.put("key", key);
vars.putObject("value", value);

We use putObject() for value since put() only accepts String values. Our final variable defines how many test iterations before the current thread should print a summary. We’ll use this later:

vars.putObject("summary-iterations", 5)

4. Sampler

Our sampler retrieves the values stored earlier and sends them to the API with the Apache HTTP framework bundled with JMeter. Most importantly, we need to include import statements for any classes mentioned directly in the scripts that aren’t in the default imports list.

To create the request body, we’ll use the older new Object[]{…} syntax for String.format(), as varargs is a Java 5 feature:

url = vars.get("base-api");
json = String.format(
  "{\"key\": \"%s\", \"value\": %s}", 
  new Object[]{ vars.get("key"), vars.get("value") }
);

Now, let’s perform the request:

client = HttpClients.createDefault();
body = new StringEntity(json, ContentType.APPLICATION_JSON);

post = new HttpPost(url);
post.setEntity(body);

response = client.execute(post);

JMeter includes the ResponseCode and ResponseMessage variables, which we retrieve in later stages:

ResponseCode = response.getStatusLine().getStatusCode();
ResponseMessage = EntityUtils.toString(response.getEntity());

Finally, since we can’t use a try-with-resources block, we close our resources and choose a return value for the script:

response.close();
client.close();

return json;

The return can be of any type. We’ll return the request body to calculate the request size later.

5. Post Processor

Post Processors execute immediately after the sampler. We’ll use one to gather and aggregate the information we need. Let’s create a function to increment a variable and save the result:

incrementVar(name, increment) {
    value = (Long) vars.getObject(name);
    if (value == null) 
      value = 0l;

    value += increment;
    vars.putObject(name, value);
    log.info("{}: {}", name, value);
}

Here, we also have a logger available with no additional configuration. It’ll log to the JMeter console. Notice that visibility modifiers, return types, and argument types aren’t required; they’re inferred from context.

We’ll increment the elapsed time and bytes sent/received for each thread iteration. The prev variable allows us to get that information from the previous script:

incrementVar("elapsed-time-total", prev.getTime());
incrementVar("bytes-received-total", prev.getResponseMessage().getBytes().length);
incrementVar("bytes-sent-total", prev.getBytesAsLong());

6. Listener

Listeners run after the PostProcessor. We’ll use one to write a report to the file system. First, let’s write a helper function and set a few variables:

println(writer, message, arg1) {
    writer.println(String.format(message, new Object[] {arg1}));
}

thread = ctx.getThread();
threadGroup = ctx.getThreadGroup();

request = prev.getResponseDataAsString();
response = prev.getResponseMessage();

The ctx variable provides information from the Thread Group and current thread. Next, let’s build a FileWriter to write our report in the home directory:

fw = new FileWriter(new File(System.getProperty("user.home"), "jmeter-report.txt"), true);
writer = new PrintWriter(new BufferedWriter(fw));

println(writer, "* iteration: %s", vars.getIteration());
println(writer, "* elapsed time: %s ms.", prev.getTime());
println(writer, "* request: %s", request);
println(writer, "= %s bytes", prev.getBytesAsLong());
println(writer, "* response body: %s", response);
println(writer, "= %s bytes", response.getBytes().length);

Since this runs for every thread iteration, we call vars.getIteration() to track the iteration count. Lastly, if the current iteration is a multiple of summary-iterations, we print a summary:

if (vars.getIteration() % vars.getObject("summary-iterations") == 0) {
    println(writer, "## summary for %s", thread.getThreadName());
    println(writer, "* total bytes sent: %s bytes", vars.get("bytes-sent-total"));
    println(writer, "* total bytes received: %s bytes", vars.get("bytes-received-total"));
    println(writer, "* total elapsed time: %s ms.", vars.get("elapsed-time-total"));
}

Finally, let’s close the writer:

writer.close();

7. Conclusion

In this article, we explored how to effectively use BeanShell in JMeter to add custom scripting to test plans. We covered vital components like PreProcessors, Samplers, PostProcessors, and Listeners, showcasing how to manipulate request data, handle responses, and log metrics.

As always, the source code is available over on GitHub.