Create the appium session by passing appium:intercept : true
option in the desired capability. Once the session is successfully created, tests can manage the api mocking using below commands.
Mock configuration is a json object that defines the specification for filtering and applying various updates to the api request and below is the structure for the config object.
{
url: string;
method?: string;
updateUrl?: [{ regexp: string, value: string}];
headers?: object | { add: object : string, remove: string[]};
requestBody?: string;
updateRequestBody?: [{regexp: string, value: string }] | [{jsonPath: string, value: string}];
statusCode?: number;
responseHeaders?: object | { add: object : string, remove: string[]};
responseBody?: string;
updateResponseBody?: [{regexp: string, value: string }] | [{jsonPath: string, value: string}];
}
Name | Type | Required | Description | Example |
---|---|---|---|---|
url | string | yes | Regular Expression or Glob pattern matcher to filter the request for applying the mock | Sample url : https://www.reqres.in/api/users?page=1 Regex example: /api/users?.*/g Glob pattern: **/api/users*?* |
method | string | no | Method to matching the request for applying the mock | GET / POST / PUT / PATCH |
updateUrl | string | no | Regular Expression patter and replaceable value to update the outgoing url | Sample url : https://www.reqres.in/api/users?page=1 When passing { updateUrl : {regexp: "/page=(\\d)+/g", value: "page=5"} } Then outgoing url will be replaced to https://www.reqres.in/api/users?page=2 |
headers | object | no | Map of key value pairs to be added or removed from the request header | 1. Passing a plain object map will add all the key: value pairs to the request headers {headers : {"Content-Type" : "application/json", "Authorization": "Bearer sometoken"} } 2. If you want to add and remove header values simulaneously then you can pass the header as {headers : { add : {"Content-Type" : "application/json"}, remove: ["Authorization", "X-Content-Type"] } } |
requestBody | string | no | This will replace the original payload (post body) send to api the from the application and updates it with new body | When passing {"url" : "/api/login/g" , "requestBody": "{\"email\": \"[email protected]\", \"password\": \"wrongpassword\"}"} will send the given payload for all login api calls made from the application |
updateRequestBody | string | no | This is similar to requestBody but instead of fully replacing the request payload, you can replace any value in the payload using Regular expression or jsonpath | Consider you application sending {\"username\": \"someusername\", \"email\": \"[email protected]\", \"password\": \"somepassword\", \"isAdmin\" : \"false\"} as a payload for a register user api request and you want to update the email and username, then you can pass {"updateRequestBody": [{ "jsonPath": "$.email", "[email protected]" }, { "jsonPath": "$.username", "new_username" }]} and it will update the email and username field before sending the request to the server |
statusCode | number | no | Updates the response status code with the given value | To simulate any unexpected error you can send some of the below statusCode 1. 500 - Internal server error 2. 400 - Bad request 3. 401 - Unauthorized |
responseHeaders | object | no | Map of key value pairs to be added or removed from the response header | Same syntax as headers key. But this will update the response header |
responseBody | object | no | This will replace the original response data returned by the api server and updates it with new data | Passing the config as {"url" : "/api/login/g" , "responseBody": "{\"error\": \"User account locked\"}", statusCode: 400 } will simulate a error scenario when logged in with any user credentilas |
updateResponseBody | string | no | This is similar to responseBody but instead of fully mocking the server response, you can replace any value in the response using Regular expression or jsonpath | Consider you application returns user data as {\"username\": \"someusername\", \"email\": \"[email protected]\", \"isAdmin\" : \"false\"} as a response for get user api request and you want to update the values for email and IsAdmin fiels, then you can pass {"updateRequestBody": [{ "jsonPath": "$.email", "[email protected]" }, { "jsonPath": "$.isAdmin", "true" }]} and it will update the email and isAdmin field before sending the response back to the application |
Add a new mock specification for intercepting and updating the request. The command will returns a unique id for each mock which can be used in future to delete the mock at any point in the test.
Note: Below example uses wedriver.io javascript client. For Java client you need to use ((JavascriptExecutor) driver).executeScript()
for executing commands instead of driver.execute()
const authorizationMock = await driver.execute("interceptor: addMock", [{
config: {
url: "**/reqres.in/**",
headers: {
"Authorization" : "Bearer bearertoken"
}
}
}]);
const userListGetMock = await driver.execute("interceptor: addMock", [{
config: {
url: "**/reqres.in/api/users",
method: "GET",
responseBody: JSON.stringify({
page: 2,
count: 2,
data: [
{
"first_name" : "User",
"last_name" : "1"
},
{
"first_name" : "User",
"last_name" : "2"
}
]
})
}
}]);
authorizationMock
will be executed for all api calls made to reqres.in
domain and userListGetMock
will be applied for https://www.reqres.in/api/users
with GET
http method.
Given a mockId return during addMock command, will remove the mock configuration from the proxy sever.
const authorizationMock = await driver.execute("interceptor: addMock", [{
config: {
url: "**/reqres.in/**",
headers: {
"Authorization" : "Bearer bearertoken"
}
}
}]);
//peform user action
//perform validation
..
..
await driver.execute("interceptor: removeMock", [{
id: authorizationMock
}]);
// authorizationMock will not be active after this point and the test will proceed with normal flow
Start listening for all network traffic (API calls) made by the device during a session
await driver.execute("interceptor: startListening");
// perform some action
// ...
It also supports filtering the request based on the url. include
will only listents for requests that macthes the given url pattern and exclude
will listen for all api's that doesn't match the url pattern.
await driver.execute("interceptor: startListening", [{
config: {
include : {
url: "**/reqres.in/**",
}
}
}]);
// perform some action
// ...
await driver.execute("interceptor: startListening", [{
config: {
exclude : {
url: "**/reqres.in/**",
}
}
}]);
// perform some action
// ...
Stops listening for networks traffic and return all previously recorded api calls.
await driver.execute("interceptor: startListening");
// perform some action
// ...
const apiRequests = await driver.execute("interceptor: stopListening");
stopListening command will retunrs an array of network details in the below JSON format
[
{
"requestBody": "",
"requestHeaders": {
"host": "reqres.in",
"connection": "keep-alive",
"content-length": "41",
"sec-ch-ua": "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"",
"sec-ch-ua-mobile": "?1",
"user-agent": "Mozilla/5.0 (Linux; Android 12; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36",
"content-type": "application/json",
"accept": "*/*",
"origin": "https://reqres.in",
"sec-fetch-site": "same-origin",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
"referer": "https://reqres.in/",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
"cookie": "_gid=GA1.2.1828776619.1706164095; __stripe_mid=3d0fd295-9d68-4d75-bdb2-55b809fb49ed8dba35; __stripe_sid=466b9d3a-2d7b-4f48-9c66-ca859c8d342f06a86f; _gat=1; _gat_gtag_UA_174008107_1=1; _ga_CESXN06JTW=GS1.1.1706164096.1.1.1706166680.0.0.0; _ga=GA1.1.546181777.1706164095; _ga_WSM10MMEKC=GS1.2.1706164097.1.1.1706166681.0.0.0"
},
"url": "https://reqres.in/api/users/2",
"method": "PUT",
"responseBody": "{\"name\":\"morpheus\",\"job\":\"zion resident\",\"updatedAt\":\"2024-01-25T07:24:58.607Z\"}",
"responseHeaders": {
"http/1.1 200 ok": "HTTP/1.1 200 OK",
"date": "Thu, 25 Jan 2024 07:24:58 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "close",
"report-to": "{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1706167498&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=OTZf6wqjMxJtlD7uxpJC1eBUfbrlcO7RrUKeTeefoG0%3D\"}]}",
"reporting-endpoints": "heroku-nel=https://nel.heroku.com/reports?ts=1706167498&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=OTZf6wqjMxJtlD7uxpJC1eBUfbrlcO7RrUKeTeefoG0%3D",
"nel": "{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}",
"x-powered-by": "Express",
"access-control-allow-origin": "*",
"etag": "W/\"50-XmcMaub9BFf/y9879X3p35X0L4c\"",
"via": "1.1 vegur",
"cf-cache-status": "DYNAMIC",
"vary": "Accept-Encoding",
"server": "cloudflare",
"cf-ray": "84aec7909fed601c-SIN"
},
"statusCode": 200
},
{
"requestBody": "",
"requestHeaders": {
"host": "reqres.in",
"connection": "keep-alive",
"sec-ch-ua": "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"",
"sec-ch-ua-mobile": "?1",
"user-agent": "Mozilla/5.0 (Linux; Android 12; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36",
"content-type": "application/json",
"accept": "*/*",
"origin": "https://reqres.in",
"sec-fetch-site": "same-origin",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
"referer": "https://reqres.in/",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
"cookie": "_gid=GA1.2.1828776619.1706164095; __stripe_mid=3d0fd295-9d68-4d75-bdb2-55b809fb49ed8dba35; __stripe_sid=466b9d3a-2d7b-4f48-9c66-ca859c8d342f06a86f; _gat=1; _gat_gtag_UA_174008107_1=1; _ga_CESXN06JTW=GS1.1.1706164096.1.1.1706166680.0.0.0; _ga=GA1.1.546181777.1706164095; _ga_WSM10MMEKC=GS1.2.1706164097.1.1.1706166681.0.0.0"
},
"url": "https://reqres.in/api/users/2",
"method": "DELETE",
"responseBody": "",
"responseHeaders": {
"http/1.1 204 no content": "HTTP/1.1 204 No Content",
"date": "Thu, 25 Jan 2024 07:24:59 GMT",
"content-length": "0",
"connection": "close",
"report-to": "{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1706167499&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=GzTutDCgQC4QQ%2BomNat%2BqJScD%2BtwfgViqmG7fz6%2F9yk%3D\"}]}",
"reporting-endpoints": "heroku-nel=https://nel.heroku.com/reports?ts=1706167499&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=GzTutDCgQC4QQ%2BomNat%2BqJScD%2BtwfgViqmG7fz6%2F9yk%3D",
"nel": "{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}",
"x-powered-by": "Express",
"access-control-allow-origin": "*",
"etag": "W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"",
"via": "1.1 vegur",
"cf-cache-status": "DYNAMIC",
"server": "cloudflare",
"cf-ray": "84aec7977f6c9f77-SIN"
},
"statusCode": 204
}
]
Map<String, String> config = new HashMap();
config.put("url", "/api/users?.*");
config.put("responseBody","{\n" +
" \"page\": 2,\n" +
" \"per_page\": 6,\n" +
" \"total\": 12,\n" +
" \"total_pages\": 2,\n" +
" \"data\": [\n" +
" {\n" +
" \"id\": 7,\n" +
" \"email\": \"[email protected]\",\n" +
" \"first_name\": \"Michael\",\n" +
" \"last_name\": \"Lawson\",\n" +
" \"avatar\": \"https://reqres.in/img/faces/7-image.jpg\"\n" +
" }\n" +
" ]\n" +
"}");
((JavascriptExecutor)DriverManager.getDriver()).executeScript("interceptor: addMock", new HashMap(){{put("config", config); }});
driver.findElement(By.xpath("//android.widget.TextView[contains(@text,'List')]")).click();
// Create the remove headers list
List<String> removeHeaders = new ArrayList<>();
removeHeaders.add("cookie");
// Create the headers object
Map headers = new HashMap();
headers.put("remove", removeHeaders);
infoLogs("** add mock code here **");
Map<String, Object> config = new HashMap();
config.put("url", "**/api/lobbyApi/v1/getMatches");
config.put("headers",headers);
Object id= ((JavascriptExecutor) driver).executeScript("interceptor: addMock", new HashMap() {{
put("config", config);
}});
ObjectMapper objectMapper = new ObjectMapper();
ArrayNode updateRequestBodyArray = objectMapper.createArrayNode();
// Update sportsType to 1,so that all the sports tab should show the same data.
ObjectNode sportsType = objectMapper.createObjectNode();
sportsType.put("jsonPath", "$.sportsType");
sportsType.put("value", "1");
updateRequestBodyArray.add(sportsType);
List<Map<String, Object>> updateRequestBodyList = new ArrayList<>();
for (int i = 0; i < updateRequestBodyArray.size(); i++) {
Map<String, Object> map = objectMapper.convertValue(updateRequestBodyArray.get(i), Map.class);
updateRequestBodyList.add(map);
}
Map<String, Object> config = new HashMap<>();
config.put("url", "**/api/lobbyApi/v1/getMatches");
config.put("updateRequestBody", updateRequestBodyList);
infoLogs("Step 2: Add Mock Interceptor");
Object id= ((JavascriptExecutor) driver).executeScript("interceptor: addMock", new HashMap<String, Object>() {{
put("config", config);
}});
ArrayNode updateRequestBodyArray = objectMapper.createArrayNode();
updateRequestBodyArray.add(createUpdateBodySpec
("$.matches.1[0].team1.dName", "Appium"));
updateRequestBodyArray.add(createUpdateBodySpec
("$.matches.1[0].team2.dName", "Selenium"));
updateRequestBodyArray.add(createUpdateBodySpec
("$.matches.1[0].seriesName", "The Official Appium Conference 2024"));
List<Map<String, Object>> updateRequestBodyList = new ArrayList<>();
for (int i = 0; i < updateRequestBodyArray.size(); i++) {
Map<String, Object> map = objectMapper.convertValue(updateRequestBodyArray.get(i), Map.class);
updateRequestBodyList.add(map);
}
Map<String, Object> config = new HashMap<>();
config.put("url", "**/api/lobbyApi/v1/getMatches");
config.put("updateResponseBody", updateRequestBodyList);
Object id= ((JavascriptExecutor) driver).executeScript("interceptor: addMock", new HashMap<String, Object>() {{
put("config", config);
}});
Map<String, Object> config = new HashMap();
config.put("url", "**/api/fl/auth/v3/getOtp");
config.put("statusCode", Integer.valueOf(500));
Object id= ((JavascriptExecutor) driver)
.executeScript("interceptor: addMock",
new HashMap() {{put("config", config); }});
infoLogs("** Remove the Mock **");
((JavascriptExecutor) driver)
.executeScript("interceptor: removeMock", new HashMap() {{
put("id", id);
}});
Below code shows how to add the mock and get the id
Object id= ((JavascriptExecutor) driver)
.executeScript("interceptor: addMock",
new HashMap() {{put("config", config); }});