使用 Restito 工具进行 REST 客户端测试:什么是 REST 客户端?

什么是 REST?

REST 代表“表征性状态传输”(REpresentational State Transfer),这是一种在给定时间点,任意两个系统之间进行通信的新方式。其中一个系统称为“REST 客户端”,另一个称为“REST 服务器”。

在学习用于 REST 客户端测试的 Restito 框架之前,让我们先学习一些基础知识。

什么是 REST 客户端?

REST 客户端是一种调用由任何系统或服务提供商暴露用于通信的 REST 服务 API 的方法或工具。例如:如果有一个 API 用于从 Google 获取某条路线的实时交通信息,那么调用 Google 交通 API 的软件/工具就称为 REST 客户端。

什么是 REST 服务器?

它是由任何系统或服务提供商暴露用于通信的方法或 API。例如,Google 暴露了一个 API 以获取给定路线的实时交通信息。

在这里,Google 服务器需要启动并运行,才能监听来自不同客户端对暴露的 API 的任何请求。

示例

是时候从上述定义中建立一个完整的端到端场景了。

让我们以像 Uber 这样的出租车预订应用程序为例,该公司需要实时了解给定车辆所在路线的交通状况。

REST 客户端

这里的客户端是司机登录的 Uber 移动应用程序。这个应用程序向 Google 地图暴露的 REST API 发送请求,以获取实时数据。例如,一个 HTTP GET 请求。

REST 服务器

在这个例子中,Google 是服务提供商,Google 地图的 API 会响应 Uber 应用程序的请求,提供所需的详细信息。

在 REST 通信中,客户端和服务器同等重要。

在这里,我们只实现了 REST 客户端的自动化测试示例。有关 REST 服务器测试,请参阅 https://guru99.com.cn/top-6-api-testing-tool.html

什么是 Restito?

Restito 是一个由 Mkotsur 开发的框架。它是一个轻量级应用程序,可帮助您执行任何类型的 HTTP 请求。您可以使用 Restito 测试您的 REST API,并查找应用程序或网络中的问题。

如何使用 Restito 测试 REST 客户端?

让我们将练习分为以下 4 个步骤

  1. 创建 HTTP 客户端和方法,向任何服务器端点发送 HTTP GET 请求。目前,假设端点是 https://:9092/getevents
  1. 启动 Restito 服务器,监听并捕获发送到 localhost https://:9092/getevents 中“getevents”端点的请求。
  1. 创建一个测试类来测试上述客户端。调用 HTTP 客户端的“sendGETRequest”方法,向 API“getevents”发起 GET 请求。
  1. 使用 Restito 框架验证 HTTP GET 调用。

让我们深入探讨上述每个步骤。

步骤 1) 创建 HTTP 客户端和方法,向任何服务器端点发送 HTTP GET 请求。

========== JAVA 代码开始 ===========

package com.chamlabs.restfulservices.client;

import java.util.HashMap;
import java.util.Map;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.json.JSONObject;

/**
 * This class creates a HTTP Client and has a method to send HTTP GET request: 
 * 		sendGETRequest(..)
 */
public class RestClient {
	/**
	 * Constructor for the  class RestClient	
	 */
	public RestClient() {
		System.out.println("Creating RestClient constructor");
	}

	/**
	 * Method to Send GET request to https://:<<port>>/getevents
	 * @param port
	 * @return true if GET request is successfully sent. False, otherwise.
	 */
	public static boolean sendGETRequest(int port) {
		try {
			HttpClient client = HttpClientBuilder.create().build();
			HttpGet getRequest = new HttpGet("https://:" + port + "/getevents");
			//HttpResponse response = client.execute(request);
			client.execute(getRequest);
			System.out.println("HTTP request is sent successfully."
					+ "Returning True");
			return true;
		} 
		
		catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Some exception has occurred during the HTTP Client creation."
				+ "Returning false");
		return false;
	}
}

========== JAVA 代码结束 ===========

步骤 2) 启动 Restito 服务器,监听并捕获发送到 localhost https://:9092/getevents 中“getevents”端点的请求。

========== JAVA 代码开始 ===========

package com.chamlabs.restfultesting.util;

import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;
import static com.xebialabs.restito.semantics.Action.status;
import static com.xebialabs.restito.semantics.Condition.get;
import static com.xebialabs.restito.semantics.Condition.post;
import java.util.List;
import org.glassfish.grizzly.http.util.HttpStatus;
import com.xebialabs.restito.semantics.Call;
import com.xebialabs.restito.server.StubServer;

/**
 * This utility class contains several utility methods like : 
 * 		restartRestitoServerForGETRequests(..)
 * 		restartRestitoServerForPOSTRequests(..) 
 * 		waitAndGetCallList(..)
 * 
 * @author cham6
 * @email: paperplanes.chandra@gmail.com
 * @fork: https://github.com/cham6/restfultesting.git
 *
 */
public class TestUtil {
	
	/**
	 * Utility method to start restito stub server to accept GET requests
	 * @param server
	 * @param port
	 * @param status
	 */
	public static void restartRestitoServerForGETRequests (StubServer server, int port, HttpStatus status)
    {
        // Kill the restito server
        if (server != null) {
            server.stop();
        }
        // Initialize and configure a newer instance of the stub server
        server = new StubServer(port).run();
        whenHttp(server).match(get("/getevents")).then(status(status));
    }
	
	/**
	 * Utility method to start restito stub server to accept POST requests
	 * @param server
	 * @param port
	 * @param status
	 */
	public static void restartRestitoServerForPOSTRequests (StubServer server, int port, HttpStatus status)
    {
        // Kill the restito server
        if (server != null) {
            server.stop();
        }
        // Initialize and configure a newer instance of the stub server
        server = new StubServer(port).run();
        whenHttp(server).match(post("/postevents")).then(status(status));
    }
	
	/**
     * For a given restito stub server, loop for the given amount of seconds and
     * break and return the call list from server.
     * 
     * @param server
     * @param waitTimeInSeconds
     * @return
     * @throws InterruptedException
     */
	public static List<Call> waitAndGetCallList (StubServer server, int waitTimeInSeconds)
        throws InterruptedException
    {
        int timeoutCount = 0;
        List<Call> callList = server.getCalls();
        while (callList.isEmpty()) {
            Thread.sleep(1000);
            timeoutCount++;
            if (timeoutCount >= waitTimeInSeconds) {
                break;
            }
            callList = server.getCalls();
        }
        // Wait for 2 seconds to get all the calls into callList to Eliminate any falkyness.
        Thread.sleep(2000);
        return server.getCalls();
    }


}

========== JAVA 代码结束 ===========

步骤 3) 创建一个测试类来测试上述客户端。调用 HTTP 客户端的 sendGETRequest 方法,向 API“getevents”发起 GET 请求。

========== JAVA 代码开始 ===========
import junit.framework.TestCase;

import com.chamlabs.restfulservices.client.RestClient;
import com.chamlabs.restfultesting.util.TestUtil;
import com.xebialabs.restito.semantics.Call;
import com.xebialabs.restito.server.StubServer;
import static org.glassfish.grizzly.http.util.HttpStatus.ACCEPTED_202;
import org.json.JSONObject;
import java.util.List;
import java.util.Map;

/**
 * This class contains several junit tests to validate the RestClient operations like: 
 * 		sendRequest(..)
 * 		sendRequestWithCustomHeaders(..) 
 * 		sendPOSTRequestWithJSONBody(..)
 *
 */

public class RestClientTester extends TestCase {
	
    private static final Integer PORT = 9098;
    private static final Integer PORT2 = 9099;
    private static final Integer PORT3 = 9097;
	
    public RestClientTester() {
    	System.out.println("Starting the test RestClientTester");
    }
    /**
     * Junit test to validate the GET request from RestClient
     * Steps:
     * 		1) Create a stub server using Restito framework and configure it to listen on given port
     * 		2) Invoke the sendGETRequest(..) method of RestClient
     * 		3) Restito captures the matching GET requests sent, if any.
     * 		4) Validate if Restito has captured any GET requests on given endpoint
     * Expected Behavior:
     * 		> Restito should have captured GET request and it should have captured only one GET request.
     * Finally:
     * 		> Stop the stub server started using restito.
     */
	public void testGETRequestFromClient() {
		
		StubServer server = null;
		try {
		//This will start the stub server on 'PORT' and responds with HTTP 202 'ACCEPTED_202'
		TestUtil.restartRestitoServerForGETRequests(server, PORT, ACCEPTED_202);
		
		RestClient.sendGETRequest(PORT);
		
		List<Call> callList = TestUtil.waitAndGetCallList(server, 30);
		assertTrue("GET request is not received from the RestClient. Test failed.", 
				(callList != null) && (callList.size() == 1));
		}
		catch(Exception e) {
			e.printStackTrace();
			fail("Test Failed due to exception : " + e);
		}
		finally {
			if(server != null) {
				server.stop();
            }
		}	
	}

========== JAVA 代码结束 ===========

步骤 4) 如何使用 Restito 框架验证带有 Headers 的 GET 请求和带有 body 的 POST 请求。

========== JAVA 代码开始 ===========

/**
     * Junit test to validate the GET request with headers from RestClient
     * Steps:
     * 		1) Create a stub server using Restito framework and configure it to listen on given port
     * 		2) Invoke the sendGETRequestWithCustomHeaders(..) method of RestClient
     * 		3) Restito captures the matching GET requests sent, if any. 
     * 		4) Validate if Restito has captured any GET requests on a given endpoint
     * Expected Behavior:
     * 		> Restito should have captured GET request, and it should have captured only one GET request.
     * 		> Get the headers of the captured GET request 
     * 		  and make sure the headers match to the ones configured.
     * Finally:
     * 		> Stop the stub server started using restito.
     */

public void testGETRequestWithHeadersFromClient() {
		StubServer server = null;
		
		try {
		//This will start the stub server on 'PORT' and responds with HTTP 202 'ACCEPTED_202'
			TestUtil.restartRestitoServerForGETRequests(server, PORT2, ACCEPTED_202);
		
		RestClient.sendGETRequestWithCustomHeaders(PORT2);
		
		List<Call> callList = TestUtil.waitAndGetCallList(server, 30);
		assertTrue("GET request is not received from the RestClient. Test failed.", 
				(callList != null) && (callList.size() == 1));
		
		//Validate the headers of the GET request from REST Client
		Map<String, List<String>> headersFromRequest = callList.get(0).getHeaders();
		assertTrue("GET request contains header Accept and its value ",
				headersFromRequest.get("Accept").contains("text/html"));
		assertTrue("GET request contains header Authorization and its value ",
				headersFromRequest.get("Authorization").contains("Bearer 1234567890qwertyuiop"));
		assertTrue("GET request contains header Cache-Control and its value ",
				headersFromRequest.get("Cache-Control").contains("no-cache"));
		assertTrue("GET request contains header Connection and its value ",
				headersFromRequest.get("Connection").contains("keep-alive"));
		assertTrue("GET request contains header Content-Type and its value ",
				headersFromRequest.get("Content-Type").contains("application/json"));
		}
		catch(Exception e) {
			e.printStackTrace();
			fail("Test Failed due to exception : " + e);
		}
		finally {
			if(server != null) {
				server.stop();
            }
		}
	}
/**
     * Junit test to validate the POST request with body and headers from RestClient
     * Steps:
     * 		1) Create a stub server using Restito framework and configure it to listen on given port
     * 		2) Invoke the sendPOSTRequestWithJSONBody(..) method of RestClient
     * 		3) Restito captures the matching POST requests sent, if any.
     * 		4) Validate if Restito has captured any POST requests on given endpoint
     * Expected Behavior:
     * 		> Restito should have captured POST request and it should have captured only one POST request.
     * 		> Get the body of the captured POST request and validate the JSON values
     * Finally:
     * 		> Stop the stub server started using restito.
	 */

public void testPOSTRequestWithJSONBody() {
		StubServer server = null;
		
		try {
		//This will start the stub server on 'PORT' and responds with HTTP 202 'ACCEPTED_202'
			TestUtil.restartRestitoServerForPOSTRequests(server, PORT3, ACCEPTED_202);
		
		RestClient.sendPOSTRequestWithJSONBody(PORT3);
		
		List<Call> callList = TestUtil.waitAndGetCallList(server, 30);
		assertTrue("POST request is not received from the RestClient. Test failed.", 
				(callList != null) && (callList.size() == 1));
		
		//Validate the headers of the GET request from REST Client
		
		String requestBody = callList.get(0).getPostBody();
		JSONObject postRequestJSON = new JSONObject(requestBody);
		assertTrue("The timeUpdated in json is incorrect",
				postRequestJSON.get("timeUpdated").toString().equalsIgnoreCase("1535703838478"));
		assertTrue("The access_token in json is incorrect",
				postRequestJSON.get("access_token").toString().
				equalsIgnoreCase("abf8714d-73a3-42ab-9df8-d13fcb92a1d8"));
		assertTrue("The refresh_token in json is incorrect",
				postRequestJSON.get("refresh_token").toString().
				equalsIgnoreCase("d5a5ab08-c200-421d-ad46-2e89c2f566f5"));
		assertTrue("The token_type in json is incorrect",
				postRequestJSON.get("token_type").toString().equalsIgnoreCase("bearer"));
		assertTrue("The expires_in in json is incorrect",
				postRequestJSON.get("expires_in").toString().equalsIgnoreCase("1024"));
		assertTrue("The scope in json is incorrect",
				postRequestJSON.get("scope").toString().equalsIgnoreCase(""));
		}
		catch(Exception e) {
			e.printStackTrace();
			fail("Test Failed due to exception : " + e);
		}
		finally {
			if(server != null) {
				server.stop();
            }
		}
	}
}

========== JAVA 代码结束 ===========

使用 Restito 框架进行 REST 客户端测试的优点

以下是 Restito 框架用于 REST 客户端测试的优点/好处

  • 我们不需要开发实际的 REST 服务器来测试 REST 客户端。
  • Restito 提供了强大而多样的实用程序和方法来模拟服务器的不同行为。例如:测试当服务器响应 HTTP 404 错误或 HTTP 503 错误时 REST 客户端的行为。
  • Restito 服务器可以在几毫秒内启动,并在测试完成后终止。
  • Restito 支持所有类型的 HTTP 方法内容,如压缩、非压缩、统一、application/text、application/JSON 等。

使用 Restito 框架进行 REST 客户端测试的缺点

以下是 Restito 框架用于 REST 客户端测试的缺点

  • REST 客户端源代码应进行调整,以将“localhost”视为服务器机器。
  • 如果在任何端口上打开服务器,可能会与一些常用端口(如“8080”或“9443”等)发生冲突。
  • 建议使用 9092 或 9099 等不常用端口,这些端口不被其他工具普遍使用。

摘要

  • REST 代表“表征性状态传输”,这是一种在给定时间点,任意两个系统之间进行通信的新标准方式。
  • REST 客户端是一种调用由任何系统或服务提供商暴露用于通信的 REST 服务 API 的方法或工具。
  • 在 RestServer 中,它是由任何系统或服务提供商暴露用于通信的方法或 API。
  • Restito 是一个轻量级应用程序,可帮助您执行任何类型的 HTTP 请求
  • 创建 HTTP 客户端和方法,向任何服务器端点发送 HTTP GET 请求
  • 启动 Restito 服务器,监听并捕获发送到“getevents”端点的请求。
  • 启动 Restito 服务器,监听并捕获发送到 localhost 中“getevents”端点的请求
  • 在这里,我们只实现了 REST 客户端的自动化测试示例。
  • 我们不需要开发实际的 REST 服务器来测试 REST 客户端。
  • REST 客户端源代码应进行调整,以将“localhost”视为服务器机器。