BreadcrumbHomeResourcesBlog How To Do Advanced Request Matching With BlazeMeter July 5, 2022 How to Do Advanced Request Matching With BlazeMeterService VirtualizationBy Petr VlasekRecently, BlazeMeter introduced data-driven Mock Services to the market. It is a very powerful tool to let your Mock Services return specific data in responses based on a provided data model built using synthetic data or CSV files. It is extremely important to keep mock data consistent with the test data used for tests where mock services participate. In this two-part blog series, we will introduce two additional concepts that make Mock Services smart, reusable and powerful: advanced request matching and dynamic responses, sometimes called “magic strings.” In the first part, we will focus on advanced request matching. Table of ContentsRequest Matching – Setting the Stage Basic Request Body MatchersAdvanced Request Body MatchersBottom Line: Request Matching and General Matching Rules Table of Contents1 - Request Matching – Setting the Stage 2 - Basic Request Body Matchers3 - Advanced Request Body Matchers4 - Bottom Line: Request Matching and General Matching Rules Back to topRequest Matching – Setting the Stage Before we deep dive into complex matching scenarios, let’s first recap various request matching options that are available. All these request matching options are also described in detail in the BlazeMeter Documentation. First, there is an HTTP operation and path matcher that allows you to define if your Mock Service should respond to queries such as GET /users, POST /order, or DELETE /user/. Essentially, you can define which HTTP operation to match and whether to match the path by a specific or regular expression-enabled matching. Second, you can match requests by headers, query parameters, and cookies in the following ways: Specific (equals) match. Specific but case-insensitive match (equals case insensitive). Matching whether the actual value contains a defined substring (contains text). Or Whether the value matches or does not match the regular expression pattern (matches regex, does not match regex). Whether a specific field, such as a header or query parameter is missing from the request (is absent). Just to note that to match if a particular field is present, say whether header "User-agent" is in the request, a regular expression matcher “matches regex” matching “.+” (non-empty value) could be used. In this case, we care if the header has a value, but we do not care about the specifics of the value itself. In addition, it is also possible to match requests with authentication credentials. The last request matching option is to match the request body. In fact, there is a variety of special request body matches that can help you to achieve more advanced objectives. Let’s delve into these more advanced options further. Back to topBasic Request Body MatchersThe simplest request body matcher is equals (and equals case insensitive). These essentially match the exact equality of the entire request body payload. Keep in mind though, that even a small difference between the actual content of the request body and what is expected to match (e.g. a new line or an extra space) causes no match. Therefore, these simpler request body matchers are very fragile when dealing with JSON, XML, or other structured request body payloads, and are not practical choices in these cases. Then there is a family of partial matchers: contains text, matches the regex, and does not match regex. This group of matchers is more flexible since you can match for the presence of just the desired substring or some specific regular expression pattern in the request body content. This makes partial matchers less sensitive to formatting differences or whitespaces. Yet, partial matchers are not sufficient for looking for a specific node in XML or JSON. Plus, they are not helpful when trying to match a specific structure of XML or JSON body payloads. The Is absent matcher works when the request contains no request body. To determine whether a request has a body, use matches regex with anything non-empty - i.e., the .+ regular expression pattern. Back to topAdvanced Request Body MatchersDealing with XML or JSON Request Body Content In cases where there is a need to match the content of XML or JSON documents semantically while ignoring the format, Equals XML or Equals JSON are the matchers you want to use. These matchers ignore whitespace and formatting differences, and even differences in the order of the fields within the documents. So, these matchers use the “semantic” content of these documents instead of relying on character-level equality. Let’s look at an example. The Equals to JSON matcher will consider these two requests as the same, while the simple Equals matcher will not:{ "id":"1", "state":"active" } and {"state":"active","id":"1"} Notice the difference between these two. The first matcher is multiline, with "id" coming before "state." The second is a single line, and the order of fields is reversed. Clearly, different strings, whitespaces, and order do not make any difference. From the JSON content point of view, these two payloads are considered equal. Partial Matching of XML and JSON Request Body Content Sometimes, teams need to match JSON or XML fields that contain specific values, while the rest of the fields can have any value. There are also instances where teams need to match the body's specific structure. Using JSON and XML unit placeholders is the best way to go about request matching in these cases. Within the Equals to JSON and Equals to XML matchers it is possible to use placeholders like ${json-unit.any-string}, ${json-unit.any-number}, ${json-unit.ignore} and other JSON Unit declarations. Similarly for XML, the placeholders are ${xmlunit.isNumber}, ${xmlunit.ignore} and other declarations from XML Unit. Using these placeholders, it is possible to define an Equals to JSON body matcher with this type of definition:{ "name": "John", "membership": "${json-unit.any-string}", "email": "${json-unit.ignore-element}" } In addition to matching a JSON regardless of the whitespaces, formatting, and order of elements, this definition will also: Define the field "name" in the request body as equal to John. Note that the "Membership" field is present, but can be any string. Note to ignore both the presence and value of "Email" for matching (it does not even need to be considered as part of the JSON payload).The JSON and XML unit placeholders offer a lot of flexibility to make your request matching rules become aware of the JSON or XML structure and not only match by the pure value equality. Matching by Specific JSON Field or XML Node Values There is often a need to provide a response based on a specific field or node value. For example, let us imagine a large JSON document that is sent in the request body – many fields, nested structures, and a lot of content altogether. But you are interested in one specific field and its value to match. Say you are interested in matching the value of “status” field and do not care about the rest. For these cases, using JSON unit matchers can be too cumbersome. Regular expression matching can do the trick, but it may be difficult to formulate a regular expression and maintain it if there are changes in JSON. But there is an easier and more convenient way: using JSONPath or XPath matchers. Both JSONPath and XPath matchers help you focus on a specific element, extract its value, and use it for matching by providing various ways to locate specific parts of the JSON or XML document. For example, these matchers locate by the field or node name, or even by its relative placement in the document structure – e.g., “the last child element of the second child of order element”. In general, JSONPath and XPath are very powerful query mechanisms that satisfy all needs to locate very specific elements in the document. In our case, let’s focus on the most typical example of locating a specific attribute and matching it by its value. Consider the following example of JSON body. Notice that it contains various fields and hierarchical, nested structures:{ "user_id":"3", "user_details": { "name": "John Doe", "email": "john.doe@acme.org", "status": "ACTIVE" } } We would like to match the request by the value of the "status" field to the "NOT_REGISTERED" value. BlazeMeter provides a simple way to achieve that. After you select the “matches JSON Path” or “matches XPath” request body matcher, you can type your desired JSONPath or XPath expression. Plus, there is the “Selection Wizard” action, which allows BlazeMeter to create JSONPath and XPath matching expressions for you. Just open that “Selection Wizard” and copy and paste an example of your JSON or XML document to the “Sample JSON” or “Sample XML.”The Selection Wizard recognizes the structure of your document and displays fields and nodes that have any value to match. In our case, I selected the "status" field and changed its value from ACTIVE to NOT_REGISTRED. These actions resulted in the following JSONPath matching expression: [[$.user_details.status, equalTo(NOT_REGISTERED)]] The first part, $.user_details.status is the actual JSONPath that will point to the "status" field. The second part, equalTo(NOT_REGISTERED) is the actual value-matching part. The Selection Wizard also helps you to select “Anything” instead of “Specific.” In this case, it means you do not care about the value itself, but the field still must be present. Notice that if “Anything” is selected the resulting JSONPath expression looks like this: [[$.user_details.status, matching(.*)]] Essentially, matching(.*) matches the regular expression pattern, in this case as “anything.” But you can change it to any regular expression pattern you need. Obviously, you can skip using the Selection Wizard and write your JSONPath and XPath expressions manually. You can write JSONPath or XPath matching expressions directly (e.g. $.user_details [?(@.status == 'NOT_REGISTERED')] or by using BlazeMeter [[JSONPath/XPath, matcher]] syntax where JSONPath/XPath is the expression that locates the element in document – e.g. $.user_details.status and the matcher is either equalTo() or matching() function that declares which value to match – equalTo() expects the specific value to match as an argument, while matching() expects the regular expression inside. JSONPath and XPath matchers are critical when you are dealing with complex JSON or XML documents in request bodies, but your matching is based on a specific value (or values).Back to topBottom Line: Request Matching and General Matching Rules Remember that all request matchers defined in a single transaction must match in order to be deemed a match and return a response. Essentially, you can imagine there is the AND logic in all request matchers within a single transaction. If the OR logic must be present, you need to create multiple transactions where each one represents a single part of OR. For example, a query should return a response if the query parameter origin equals “US” or if the query parameter from equals “US.” Putting these two parts into a single transaction would mean that both query parameters would have to be present in the request and have the value of “US.” But in this case, we just need one of them to be present. The solution is to create two transactions – one matching the origin query parameter, the other matching the from query parameter. Another typical case is that it is required to return a specific response for a specific value present in the request, but a generic response for any other value in the request. An example of this is if the query parameter origin is equal to US, it then returns the response “Hi from the United States.” But if it contains any other value (but it is still present), this query is expected to return just the “Hi.” You can get around this requirement with two transactions. The first one should match the specific “US” value of the query parameter origin. This query will return “Hi from the United States.” The second transaction should use a regular expression match of the query parameter origin to match “.+” (any non-empty value), which would then return the “Hi.” But how can we ensure that if the query parameter is “US,” that the second, generic transaction does not match? Because even the value “US” is still matched by the regular expression “.+”? One option is to define a regular expression that matches everything but “US” for the second transaction. But instead of spending time building a regular expression, it is easier to use the matching priority option. Once both transactions are added to a Mock Service, it is possible to say which transaction within a particular mock service should be evaluated for matching first. The rule of thumb is that the most specific transactions should have the highest priority and the most generic ones the lowest priority. In such a case, the transaction matching “US” will be set with priority 1 (highest) and the other one matching anything will be set with priority 10 (lowest, which is the default anyway). This prioritization ensures that when “US” is part of the request it is evaluated first. Therefore the query will return the correct specific response, preventing an unwanted match by the generic matcher. In this first part of our blog series, we covered all types of request matchers including the variety of request body matchers that are essential when dealing with requests that contain JSON or XML payloads. Therefore, we now know how to tailor request matching to various needs and cases that may happen. The second part of this series will take a closer look at how to make mock responses smarter and more dynamic. You can also experience advanced request matching with BlazeMeter in action with a free trial.Start Testing Now Related Resources: Request Matching ExamplesHelper Wizards for Request Matching Wiremock request matching examples Back 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.