BreadcrumbHomeResourcesBlog How To Use Dynamic Parameters For Intelligent Mock Responses August 2, 2022 How to Use Dynamic Parameters for Intelligent Mock ResponsesService VirtualizationBy Petr VlasekIn an earlier blog, we discussed request matching challenges and best practices to match requests in a smarter way. But in many cases, you may face a need to do something smarter in your responses based on the values provided in the incoming requests.The way to create dynamic responses, or make your responses smarter, is by using dynamic parameters (also known as magic strings) or by making your mock responses data-driven. In this article, we will focus on the dynamic parameters option.Table of ContentsWhat Are Dynamic Parameters?Basic Response ParametrizationDynamic Parameters: Functions and LogicFine-Tune Processing of Request Values in ResponsesThe Ultimate Approach to Extract Values From RequestsBottom LineTable of Contents1 - What Are Dynamic Parameters?2 - Basic Response Parametrization3 - Dynamic Parameters: Functions and Logic4 - Fine-Tune Processing of Request Values in Responses5 - The Ultimate Approach to Extract Values From Requests6 - Bottom LineBack to topWhat Are Dynamic Parameters?Dynamic parameters enable you to pass values from incoming requests to responses, as well as execute logic within your responses.Back to topBasic Response ParametrizationLet us consider this simple example. Suppose your mock is serving the GET request /orders?location-filter=EU – i.e., returning a list of orders submitted for a specific location. In the response, you need to return the list of orders and the indication for which location they were submitted.If the actual content of orders does matter, we recommend using the data-driven feature in that case. But for this case, let us suppose that it is not super important which orders will be returned, but it is important to state to which location the response is being returned.So, when writing the request:GET request /orders?location-filter=EUWe want it to return the following:{ "orders": ["1", "2", "3"], "location": "EU" } So, for the location filter provided with the value EU, the value “EU” should be returned in the response. For a US location, “US” should be returned in the response. It is possible to create as many transactions as locations considered and to hardcode matcher values, as well as response content (e.g., one transaction for the EU, second for the US, third for APJ), but there is an easier and more flexible way.The trick is to avoid using hardcoded values in the response and instead refer to the value provided in the request.Consider the following example:{ "orders": ["1", "2", "3"], "location": "${request.query.location-filter}" } You can spot there the familiar ${ } pattern that is used across BlazeMeter for a parameter – it is a placeholder that could get various values depending on data, iterations, or context. In this case, we use ${request.query.location-filter}, which is a special syntax to refer a value from the request query parameter location-filter.For every request that is processed by a Mock Service, the Mock Service keeps its model – i.e., what values were passed as URL, path, query parameters, headers, body, etc., so it is possible to refer to these values in response at a later point.For example:${request.path.1} - returns the value of the second element of the request path (it is 0-indexed). As an example, in the request path /user/12345/details, the ${request.path.0} value is “user”, while the ${request.path.1} value is “12345.”${request.header.Accept} – returns the value of the Accept request header.${request.body} – returns the entire response body.Such dynamic parameters could be used in a response. Anytime a transaction is matched, the Mock Service can pass request values to the response and simulate dynamic behavior.The Mock Services UI provides a convenient way to use these dynamic expressions in JSON or XML responses. The response “Edit Wizard” displays a JSON or XML document in a hierarchical tree view structure with editable leaf node values. This way it is possible to easily locate the desired part in the response document and select from available predefined dynamic expressions or write your own.The Edit Wizard is extremely useful, especially when you deal with large JSON or XML responses, and you need to edit the value of a specific field and node. It is as easy as opening the Edit Wizard, finding the specific node using the built-in search, and editing its value to either a parameterized expression or any value. No need to navigate through the sea of content within the response body text area.Back to topDynamic Parameters: Functions and LogicThe ability to return values from various request fields at runtime is useful and in most cases is a good enough solution for your needs. But sometimes you may need more than just referring to values from the request, such as the ability to add some logic to your responses. In this section, we will show you how to use functions and conditional operators in mock responses.Let’s look at the anatomy of the following response, which combines basic dynamic references to values from incoming requests with advanced concepts of data manipulation and conditional logic.${formData request.body 'inputForm' urlDecode=true} { "id": "${request.path.1}", "name": "${capitalize inputForm.firstName}", "surname": "${capitalize inputForm.lastName}", "phone": "(XXX)-XX-${substring inputForm.phone 10 14}", ${#eq request.query.from_premium.0 "true"} "status": "gold", ${else} "status": "basic" ${/eq} }First, request.body is parsed as form data (application/x-www-form-urlencoded content type) by the formData function and stored in inputForm, a temporary variable. Using a variable helps simplify further access to the value of request.body interpreted as a form. This is nicely visible on the line with the name field where the firstName is extracted from inputForm variable instead of calling the formData request.body construct again.The surnamed line also illustrates simple data manipulation. The function capitalize simply capitalizes the first letter in the value. The line with the phone illustrates the substring function, which takes a subpart of the value according to the provided boundaries. In this example, the substring function is used for taking the last four digits from the phone number provided in the request and adding them to the semi-masked phone number returned in the response. Therefore, if (001)-22-1234 is the phone number in the request, the response will contain (XXX)-XX-1234.As you can see, a function typically takes at least one argument, and its syntax is ${functionName arguments} – e.g., ${formData request.body 'inputForm' urlDecode=true}, ${capitalize inputForm.firstName} or ${substring inputForm.phone 10 14}.The example response above also uses a conditional pattern that helps define the logic to return different status values, such as gold/basic, based on the value provided in the request. First, let us look at a generic pattern of “equals” conditional expressions.${#eq condition} To be returned when condition matches ${else} To be returned when the condition does not match ${/eq} It is a traditional “if-then-else” construct. If a condition is true, then the first “branch” is used. If not, then the second one proceeds.In our example, the condition looks like thiseq request.query.from_premium.0 "true"This condition means: “Is the value of from_premium query parameter equal to the value ‘true’?” You will notice that in this case, the already explained reference to the request object is used to get the value of the query parameter from_premium. The .0 at the end of the expression makes sure that the first value (0-based indexing) of the parameter is taken (since an array of values may be provided).Knowing that it is now easy to understand the entire conditional expression used in the example:${#eq request.query.from_premium.0 "true"} "status": "gold", ${else} "status": "basic" ${/eq} The expression sets the status field to “gold” as the response if from_premium contains the value “true.” Otherwise, it uses “basic” in the response content.There are plenty of other conditional operators besides #eq – for example #gt for “greater than” or #lte for “lower than or equals.”There are also more dynamic functions available – randomValue, abbreviate, now, and others that you can read about in more detail in the BlazeMeter Guide – Supported Helper Functions. You can also check Handlebars helpers in Wiremock’s documentation, as these handlebars are supported in BlazeMeter as well.Back to topFine-Tune Processing of Request Values in ResponsesSo far, our examples have been using request values directly. Expressions like request.path, request.query or request.body help to get specific values from various request fields and to use them in responses. Or perhaps as inputs to functions that transformed a value (e.g., the functions capitalize or substring), but we were always considering the entire value.But very often, a request body contains a big document – typically JSON or XML – and it is not particularly useful to take the entire document content and use it in a response. Most likely, your interest is in certain fields, nodes, or values from the request body content, not in the entire document.Let us look at a simple example of how to deal with JSON request bodies. (Note: The same concepts could be applied when dealing with request bodies using an XML payload.)Using the POST /user request to create a new user, here is an example request body content:POST /user { "name": "John Doe", "email": "john.doe@acme.com", "details": { "country": "US", "preferred_UI": "web" } }As a part of the API design and contract, the system returns a successful response to indicate that the user object got created and returns the following, including the e-mail and preferred_UI as a reference back in the response:Created OK: [userId=1, email=john.doe@acme.com, preferred_UI=web]There is the userId field with an ID assigned to the newly created user object, while there are also values that are expected to be taken directly from the request – email and preferred_UI.In this case, it looks tempting to use the request.body expression to get the content of the request body in the response. But request.body is just a partial answer – it does not help alone because it contains the entire request body payload. What is needed are helper functions. There is the jsonPath helper that in general takes JSON input and a JSONPath expression. As a result, it returns the value of a JSONPath expression applied to the JSON input.So essentially, we can take the request.body that contains JSON and pass it to the jsonPath function with the right JSONPath expression. This extracts what we need from the entire JSON taken from the request body with the transaction response below:Created OK: [userId=1, email=${jsonPath request.body '$.email'}, preferred_UI=${jsonPath request.body '$.details.preferred_UI'}]The ${jsonPath request.body '$.email'} expression does exactly what was described above: it takes the request body, and the JSONPath expression '$.email,' which locates the email field of the JSON document and returns its value. When used all together within the jsonPath helper, the expression extracts the value of the email field from the request body and puts it into the response.The same principle is used for the preferred_UI field. And that is it – a simple way how to “point” to a specific part of a JSON request payload (JSONPath expressions are very powerful to construct sophisticated queries) and use it in a response. This method also works with XML documents in request bodies – simply by using xPath helper and XPath expressions.There are two ways to use the hardcoded userId=1 part: either make it data-driven or use a dynamic function like this:Created OK: [userId=${randomInt lower=1 upper=999}, email=${jsonPath request.body '$.email'}, preferred_UI=${jsonPath request.body '$.details.preferred_UI'}]As a best practice, it is always good to first make sure that the request contains the values that we want to extract. So, the recommended approach to the transaction above is to add two request body matchers based on JSONPath matchers to make sure the respective content is part of the request:[[$.email, matching(.+)]][[$.details.preferred_UI, matching(.+)]]Back to topThe Ultimate Approach to Extract Values From RequestsWe now know how to deal with cases where we need to extract a specific part of the JSON or XML document provided in the request and use it in the response served by the Mock Service. But in reality, we may not always deal with properly structured JSON and XML documents.A request may contain specifically formatted payloads that are very far from being considered JSON or XML documents, but we still need to match by a particular value inside such request bodies. There is a solution for that: using powerful regular expressions.Just as we used the jsonPath and xPath helper functions to extract values from provided JSON or XML input, we can use the regexExtract helper to extract values using regular expressions.Consider POST /order request that contains request with body like this:{ itemID=[item-1234], submittedBy=[user-1], processingId=[123456], shipped=[Done] }It is a key-value structure in the format of key=[value]. It is neither JSON, nor XML.As a response to this request, we expect the following if it is successful:{ "id": "1", "processingId": "123456" }After an id is assigned and the processingId value is taken from the request we must find a way to get the value of the processingId entry from the request body to use it in a response. When the content is neither JSON or XML, the last resort is to use regular expressions, which matches specific parts of a string according to powerful rules and constructs.Regular expressions are not easy, so do not hesitate to check some basics about regular expressions first and open a regular expression debugger to interact with and try various expressions we will show later in this blog.First, let us break the problem into smaller pieces. Using the constructs where we had to deal with JSON bodies, we can intuitively follow that pattern by taking the request.body value and processing it through regexExtract using the provided regular expression.This will give us the following high-level structure of the desired expression:${regexExtract request.body regularExpression}The transaction response definition that follows the above structure looks like this:{ "id": "1", "processingId": ${regexExtract request.body 'processingId=\[(.+)\]' 'parts'} "${parts.0}" }Let us analyze how that works.1) The straightforward part of this expression is request.body as the second element in the expression. As we already know, request.body simply returns the entire body content of the request that was sent. By using regexExtract together with request.path, request.query or request.header, you can also extract content using the regular expression from values provided in the path, query parameters, or request headers.2) regexExtract is a helper that extracts a specific part from the string provided as an input argument. In our case, this helper will extract information from a value found in request.body. As I previously mentioned, the string can be extracted from any other parameter or value, such as regexExtract 'abcd' or regexExtract request.query.id.3) regexExtract extracts a value from the input string according to the provided regular expression rule that is passed as the third element in the expression. In our case, it is the regular expression processingId=\[(.+)\] wrapped in single quotes (the regular expression must be passed in single quotes).This regular expression is used to scan the entire content of the request body (i.e. the string provided to regexExtract) and point to the place where it finds processingId=[…]. In this case, we are determining whether processingId=[123456] will be matched and extracted from the content of the request body.It is important to make the “left-hand” side (the key “processingId”) part of our expression because we need its specific value. Therefore, we cannot use a regular expression that would match any “right-hand” side value because we do not care about other values, such as the value of a submittedBy key-value pair.We are now able to identify the right key-value pair, such as processingId=[123456], but we just need the value on the right-hand side and without the [ ] brackets – i.e., 123456. This is where it gets tricky.We must use the regular expression groups construct to extract this value without brackets. You can imagine that a regular expression group only considers a sub-part (its boundaries defined in parentheses) of the content that matches processingId=[123456]. By specifying our regular expression as processingId=\[(.+)\] and not just processingId=\[.+\], we declare anything inside the brackets after processingId= as a group that contains the actual number value we need. While these matchers are similar, the first one has the group parentheses inside.Note: The screenshot below illustrates using regex101.com for our example. Notice the color highlighting that indicates which part of the test string is matched and which is part of the group.Now, we can specify what exactly we need to extract from the regular expression. The value extracted by the regular expression group is stored in an array which is passed as the last part of the expression. In our case, it is named 'parts.'We have only one group defined and since the array is 0-indexed, we can access and use the value using the expression ${parts.0}, which extracts the first element in the array and puts it into the response. In our example, the response will return 123456.Putting it all together:${regexExtract request.body 'processingId=\[(.+)\]' 'parts'} extracts the desired information into the “parts” array. It does not output anything; it is just a processing logic that stores content in the ‘parts’ array."${parts.0}" is where we get the output for the response, by getting a single value from the 'parts' array and setting it in the response content.Using the regexExtract is a last resort when dealing with proprietary structures and payloads. As we have shown, it is a powerful and flexible way to match requests by specific pieces of data in request bodies and to extract specific parts into the response.Back to topBottom LineBetween dynamic parameters and advanced request matching, you now have various options and examples to make your Mock Services powerful and even more flexible than before.Combining dynamic parameters with data-driven features is an ultimate combination that adds intelligence and re-usability to your service mocking use-cases.See Mock Services in ActionTry out BlazeMeter Mock Services today.Start TestingRelated Resources: JMeter Parameterization: The Complete GuideBack to top
Petr Vlasek Product Management, BlazeMeter Petr works as Product Manager for BlazeMeter Continuous Testing platform. In past he worked in architect and product roles in areas of workload automation, application security and blockchain research. His current passion is looking for innovative ways how to take continuous testing to the next level and make it developer-friendly.