Created
April 7, 2013 02:13
-
-
Save jweden/5328575 to your computer and use it in GitHub Desktop.
REST API Testing Patterns:
http://jweden.tumblr.com/post/47331020792/what-legos-teaches-us-about-rest-api-testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Example Pattern to use to quickly create continuous Performance tests along side Functional Acceptance Tests for REST APIs | |
*/ | |
package example | |
import static org.junit.Assert.assertEquals | |
import org.junit.Test | |
/** | |
* Always good to have a parent class for test plans to centralize code across all test plans | |
*/ | |
abstract class AbstractTestPlan { | |
protected PersonClient personClient | |
AbstractTestPlan() { | |
personClient = new PersonClient() | |
} | |
} | |
class PerformanceTestPlan extends AbstractTestPlan{ | |
def maxSecondsToWaitForAllResults = 60 | |
def concurrencyLevel = 1000 | |
/** | |
* Send in concurrent requests and validate only correct response code received for all responses | |
*/ | |
@Test | |
void getPersonPerf() throws Exception { | |
// Notice no overhead for unmarshalling response payload to reduce cpu and memory overhead | |
personClient.getPerson(concurrencyLevel, HTTPResponseCodes.OK, maxSecondsToWaitForAllResults) | |
} | |
} | |
class FunctionalTestPlan extends AbstractTestPlan { | |
def maxSecondsToWaitForAllResults = 2 | |
def concurrencyLevel = 1 | |
/** | |
* Send in a single GET Person request and validate correct response code and payload | |
*/ | |
@Test | |
void getPerson() throws Exception { | |
def mockExpectedPerson = new Person(); | |
def personReceived = personClient.getPersonAndUnmarshall(concurrencyLevel, HTTPResponseCodes.OK, maxSecondsToWaitForAllResults) | |
assertEquals("Validating Person received from GET request is the one expected",mockExpectedPerson, personReceived) | |
} | |
} | |
/** | |
* The Person domain object (DTO) | |
*/ | |
class Person { | |
//Imagine code for fields such as name, address etc. | |
} | |
enum HTTPResponseCodes { | |
OK("200"), | |
BAD_REQUEST("404"), | |
//and all the other response codes used in tests too | |
} | |
/** | |
* Contains logic to send concurrent (ideally non-blocking) HTTP requests and receive responses. This class could call into other classes | |
* to generate performance statistics (average response time, average requests/second, etc.) across all the requests | |
* and, in turn, store these in a database for reporting and trending. | |
*/ | |
abstract class RestClient { | |
List<String> doGet (long concurrencyLevel, String url, HTTPResponseCodes expectedResponseCode, long maxSecondsToWaitForAllResults) { | |
//Imagine code to send http requests to specified url and return responses. Imagine a loop waiting for up to maxSecondsToWaitForAllResults for responses to finish | |
String mockResponse = "some response returned from http get"; | |
def mockResponseCode = HTTPResponseCodes.OK | |
//Do response code assert here so that we aren't duplicating response code asserts in tests -- DRY Principle | |
assertEquals("Validating response code", expectedResponseCode, mockResponseCode) | |
// Optionally store performance statistics in a database for trending purposes (not shown) | |
return [mockResponse] | |
} | |
} | |
/** | |
* Holds the business logic for requests to send and responses to receive. This is what is called from the test plan classes. | |
*/ | |
class PersonClient extends RestClient{ | |
private static final String PERSON_URL = "https://get/person" | |
private List<Person>unmarshallResponse(List<String> stringResponses) { | |
//Imagine code to unmarshall string responses into List of Persons | |
def mockPersonResponse = new Person() | |
return [mockPersonResponse]; | |
} | |
def List<Person> getPersonAndUnmarshall(long concurrencyLevel, HTTPResponseCodes expectedResponseCode, long maxSecondsToWaitForAllResults) { | |
List<String> stringResponses = doGet(concurrencyLevel, PERSON_URL, expectedResponseCode, maxSecondsToWaitForAllResults) | |
return unmarshallResponse(stringResponses) | |
} | |
def List<String> getPerson(long concurrencyLevel, HTTPResponseCodes expectedResponseCode, long maxSecondsToWaitForAllResults) { | |
return doGet(concurrencyLevel, PERSON_URL, expectedResponseCode, maxSecondsToWaitForAllResults) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment