Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing RESTful services: GoT JavaDay Kiev Edition

Testing RESTful services: GoT JavaDay Kiev Edition

Nowadays REST is a default architectural pattern for developing web services. But with great power comes great responsibility! Let's talk about what should be pinpointed and tested on a RESTful API level, what are the tips and tricks, where you could stumble upon common problems and what are the options on writing a beautifully crafted, maintainable test suit with Java or without. Furthermore let's talk about how to automate the whole beast to fit your delivery pipeline.

Alexey Buzdin

October 15, 2016
Tweet

More Decks by Alexey Buzdin

Other Decks in Programming

Transcript

  1. We came here to kick ass and test your services,

    and you’re all out of services @AlexeyBuzdin
  2. Why API tests? • Tests routing • Tests serialization/deserialization •

    Tests server response • Can be written by a QA who does not knows the code
  3. public class SimpleServlet extends GenericServlet {
 
 public void service(ServletRequest

    req, ServletResponse res)
 throws ServletException, IOException {
 // do something in here
 }
 } Simple Servlet
  4. Simple Servlet (Annotation Driven Development … he.. he..) @Controller
 @RequestMapping("/")


    public class SimpleController {
 
 @RequestMapping(method = RequestMethod.GET)
 public String get() {
 return "Hello";
 }
 }
  5. Why API tests? • Tests routing • Tests serialization/deserialization •

    Tests server response • Can be written by a QA who does not knows the code
  6. Simple Servlet @Controller
 @RequestMapping("/")
 public class SimpleController {
 
 @RequestMapping(method

    = POST)
 public String post(MyBean bean) {
 ...
 }
 } (Annotation Driven Development … he.. he..)
  7. Why API tests? • Tests routing • Tests serialization/deserialization •

    Tests server response • Can be written by a QA who does not knows the code
  8. Why API tests? • Tests routing • Tests serialization/deserialization •

    Tests server response • Can be written by a QA who does not knows the code
  9. Hint: Decouple integrations from your app Design your integrations to

    be easily pluggable and mockable in advance, or feel the consequences
  10. Spring Integration <int:gateway id="requestGateway"
 service-interface="com.my.RequestGateway"
 default-request-channel="requestChannel"/>
 
 <int:channel id="requestChannel"/>
 


    <int-http:outbound-gateway request-channel="requestChannel"
 url="http://localhost:8080/http/receiveGateway"
 http-method="POST"
 expected-response-type="java.lang.String"/>
  11. Moco https://github.com/dreamhead/moco java -jar moco-runner.jar http -p 12306 -c foo.json

    {
 "request": {
 "json": { "foo": "bar" }
 },
 "response": { "text": "foo" }
 }
  12. WireMock http://wiremock.org/ @Rule public WireMockRule wireMockRule = new WireMockRule(8089); stubFor(get(urlEqualTo("/my/resource"))

    .withHeader("Accept", equalTo("text/xml")) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "text/xml") .withBody("<response>Some content</response>")));
  13. For DB Mocking • Substitute to an in-memory DB (Spring

    Data and JPA/ Hibernate will help here a lot) • Start up a clean instance of a DB for testing • Reuse developers instance for testing purposes • Mock data to be fetched from file system or memory
  14. For DB Mocking • Substitute to an in-memory DB (Spring

    Data and JPA/ Hibernate will help here a lot) • Start up a clean instance of a DB for testing • Reuse developers instance for testing purposes • Mock data to be fetched from file system or memory
  15. For DB Mocking • Substitute to an in-memory DB (Spring

    Data and JPA/ Hibernate will help here a lot) • Start up a clean instance of a DB for testing • Reuse developers instance for testing purposes • Mock data to be fetched from file system or memory
  16. For DB Mocking • Substitute to an in-memory DB (Spring

    Data and JPA/ Hibernate will help here a lot) • Start up a clean instance of a DB for testing • Reuse developers instance for testing purposes • Mock data to be fetched from file system or memory
  17. https://github.com/arquillian @RunWith(Arquillian.class)
 public class GreeterTest {
 
 @Deployment
 public static

    WebArchive createDeployment() {
 return ShrinkWrap.create(WebArchive.class)
 .addClasses(Greeter.class)
 .setWebXML("WEB-INF/web.xml");
 } ...
 }
  18. 3. Write a HealthCheck test https://github.com/jayway/rest-assured @Test
 public void applicationIsUp()

    {
 given().port(8888).
 when().get("/healthcheck").
 then().statusCode(200);
 }
  19. https://github.com/jayway/rest-assured RequestSpecification - build the request ResponseSpecification - contains parsed

    response given().
 param("name", "Johan").
 cookie("cookie", "is mine").
 body("{}").
 header("Accept", “application/json"). then().
 statusCode(200).
 header("Content-Type", "application/json");
 when().post(“/resource").
  20. Hint: Always make url/port configurable At some point you would

    like to run the tests against your hotfixed api or even generate some traffic on your staging environment
  21. HJson / HumanJSON https://hjson.org/ given(). body(readHjson( "name : Lannister" +

    "lore : {" + "sigil : A golden lion rampant on a crimson field." + "words : Hear Me Röar!” + "shouldBe : Twincest is Wincest!” + "}" ).toString()). when().post("/houses"). then().statusCode(201); .and().header("Location", "Iron Throne");
  22. given(). body(readHjson( "name : Lannister" + "lore : {" +

    "sigil : A golden lion rampant on a crimson field." + "words : Hear Me Röar!” + "shouldBe : Twincest is Wincest!” + "}" ).toString()). when().post("/houses"). then().statusCode(201) .and().header("Location", "Iron Throne");
  23. Accept tested • Application is started • Resource collection exists

    • Provided JSON is parsed successfully • No validation errors
  24. Created tested • Application is started • Resource collection exists

    • Provided JSON is parsed successfully • No validation errors • Resource was stored on the server
  25. given(). body(readHjson( "name : Lannister" + "lore : {" +

    "sigil : A golden lion rampant on a crimson field." + "words : Hear Me Röar!” + "shouldBe : Twincest is Wincest!” + "}" ).toString()). when().post("/houses"). then().statusCode(201); What did we miss?
  26. assertEquals("Iron Throne", location); get(location).then().statusCode(200) .and().body(".", equalTo(readHjson( "name : Lannister" +

    "lore : {" + "sigil : A golden lion rampant on a crimson field." + "words : Hear Me Röar!” + "shouldBe : Twincest is Wincest!” + "}" ).toString()); Test Actual Data
  27. Unicode is not working java.lang.AssertionError: 1 expectation failed. Response body

    doesn't match expectation. Expected: “\”words\” : \”Hear Me Röar!\”” Actual: ”words” : ”Hear Me R䡦ar!”
  28. assertEquals("Iron Throne", location); get(location).then().statusCode(200) .and().body(".", equalTo(readHjson( "name : Lannister" +

    "lore : {" + "sigil : A golden lion rampant on a crimson field." + "words : Hear Me Röar!” + "shouldBe : Twincest is Wincest!” + "}" ).toString()); Test Actual Data
  29. Hint: Never use equals to compare server response Usually you

    have tons of stuff generated automatically on the backend which value can not be predicted in advance*: createdOn, updatedOn, auto generated IDs, etc
  30. Hint: Use JSONPath and XPath to assert equality name =

    Lannister lore.sigil = … lore.words = … lore.shouldBe = … "name : Lannister" + "lore : {" + "sigil : …” + "words : ….” + "shouldBe : ….” + "}"
  31. Hint: Use JSONPath and XPath to assert equality name =

    Lannister lore.sigil = … lore.words = … lore.shouldBe = … members[0] = Cercei members[1] = Tyrion members[2] = Jamie "name : Lannister" + "lore : {" + … “}" “members : [“ + “Cercei” “Tyrion” “Jamie” “]”
  32. Hint: Implement order for your arrays or use contains matchers

    Arrays are unordered if fetched straight from DB
  33. Hint: Always prepare test data yourself You don’t want to

    have race conditions or any other unexpected trouble born from shared state.
  34. Hint: Run your tests in parallel • Configure failsafe or

    gradle config to have maxParallelForks > 1 • Organise your test scenarios into Test Suites with atomic sense
  35. More POST on a resource • Created successfully -> Created

    (201) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource already exists -> Conflict (409) • Unsupported Media Type (415)
  36. More POST on a resource • Created successfully -> Created

    (201) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource already exists -> Conflict (409) • Unsupported Media Type (415)
  37. More POST on a resource • Created successfully -> Created

    (201) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource already exists -> Conflict (409) • Unsupported Media Type (415)
  38. • Created successfully -> Created (201) • Missing a mandatory

    property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource already exists -> Conflict (409) • Unsupported Media Type (415) More POST on a resource
  39. • Created successfully -> Created (201) • Missing a mandatory

    property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource already exists -> Conflict (409) • Unsupported Media Type (415) More POST on a resource
  40. Hint: Expose all errors and conditions to the client in

    RESTful way! REST is stateless; pass errors to the client!
  41. More GET on a resource • Get existing resource ->

    OK (200) • Get non existing resource -> Not Found (404) • Unsupported Media Type (415)
  42. More GET on a resource • Get existing resource ->

    OK (200) • Get non existing resource -> Not Found (404) • Unsupported Media Type (415)
  43. More GET on a resource • Get existing resource ->

    OK (200) • Get non existing resource -> Not Found (404) • Unsupported Media Type (415)
  44. DELETE on a resource • Delete non existing resource ->

    Not Found (404) • Delete existing resource -> OK (200) • Resource is in use -> Forbidden (403)
  45. DELETE on a resource • Delete non existing resource ->

    Not Found (404) • Delete existing resource -> OK (200) • Resource is in use -> Forbidden (403)
  46. DELETE on a resource • Delete non existing resource ->

    Not Found (404) • Delete existing resource -> OK (200) • Resource is in use -> Forbidden (403)
  47. PUT on a resource • Update non existing resource ->

    Not Found (404) • Update existing resource -> OK (200) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource is in use -> Forbidden (403) • Unsupported Media Type (415)
  48. PUT on a resource • Update non existing resource ->

    Not Found (404) • Update existing resource -> OK (200) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource is in use -> Forbidden (403) • Unsupported Media Type (415)
  49. PUT on a resource • Update non existing resource ->

    Not Found (404) • Update existing resource -> OK (200) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource is in use -> Forbidden (403) • Unsupported Media Type (415)
  50. PUT on a resource • Update non existing resource ->

    Not Found (404) • Update existing resource -> OK (200) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource is in use -> Forbidden (403) • Unsupported Media Type (415)
  51. PUT on a resource • Update non existing resource ->

    Not Found (404) • Update existing resource -> OK (200) • Missing a mandatory property -> BadRequest (400) • Validation failed -> BadRequest (400) • Resource is in use -> Forbidden (403) • Unsupported Media Type (415)
  52. Hint: Invest into describing your API Either it be a

    API Doc or better an API representation in some data format
  53. swagger: 2.0 host: petstore.swagger.io schemes: [ “http" ] consumes: [

    “application/json" ] produces: [ “application/json" ] paths: { /pets: { post: { summary: Create a pet responses: { 201: { description: "Null response” } default: { description: unexpected error, schema: { $ref: “#/definitions/Error" } } …. }}}} Error: { required: [ “code", “message" ], properties: { code: { type: integer format: int32 }, message: { type: string } } }
  54. SpringFox http://springfox.github.io/springfox/ @EnableSwagger2 public class SwaggerConfiguration { @Override public void

    addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } }
  55. Other REST-assured features • form • basic • certificate •

    oauth • oauth2 Easy authentication mechanism
  56. Other REST-assured features • proxy support • JSON-Shema validation •

    Multi-value headers and cookies • Multi-part form data support • Filters
  57. Points Made • In addition to unit, integration and ui

    tests, cover at least core functionality and cases with API tests • Design your app integrations to be easily pluggable and mockable • Decouple connection settings with the test suite itself (uri, proxy settings, authentication methods, etc) • Never use equals to compare server response and try to identify the most significant properties for a the comparison, over making an all-to-all comparison
  58. Points Made • In addition to unit, integration and ui

    tests, cover at least core functionality and cases with API tests • Design your app integrations to be easily pluggable and mockable • Decouple connection settings with the test suite itself (uri, proxy settings, authentication methods, etc) • Never use equals to compare server response and try to identify the most significant properties for a the comparison, over making an all-to-all comparison
  59. Points Made • In addition to unit, integration and ui

    tests, cover at least core functionality and cases with API tests • Design your app integrations to be easily pluggable and mockable • Decouple connection settings with the test suite itself (uri, proxy settings, authentication methods, etc) • Never use equals to compare server response and try to identify the most significant properties for a the comparison, over making an all-to-all comparison
  60. Points Made • In addition to unit, integration and ui

    tests, cover at least core functionality and cases with API tests • Design your app integrations to be easily pluggable and mockable • Decouple connection settings with the test suite itself (uri, proxy settings, authentication methods, etc) • Never use equals to compare server response and try to identify the most significant properties for a the comparison, over making an all-to-all comparison
  61. Points Made • Don’t relay on existing data and prepare

    ones for testing before test starts • Design your tests to be easily parallel and sort them in a way that they won’t affect each other • Invest in having a descriptive API representation like Swagger, RAML, or API Blueprints • Use JSONPath to assert the results
  62. Points Made • Don’t relay on existing data and prepare

    ones for testing before test starts • Design your tests to be easily parallel and sort them in a way that they won’t affect each other • Invest in having a descriptive API representation like Swagger, RAML, or API Blueprints • Use JSONPath to assert the results
  63. Points Made • Don’t relay on existing data and prepare

    ones for testing before test starts • Design your tests to be easily parallel and sort them in a way that they won’t affect each other • Invest in having a descriptive API representation like Swagger, RAML, or API Blueprints • Use JSONPath to assert the results
  64. Points Made • Don’t relay on existing data and prepare

    ones for testing before test starts • Design your tests to be easily parallel and sort them in a way that they won’t affect each other • Invest in having a descriptive API representation like Swagger, RAML, or API Blueprints • Use JSONPath/XPath to assert the results
  65. Tools to look at • REST-assured for testing RESTful api

    • Awaitility to deal with asynchronous jobs • Swagger, RAML, API Blueprint for API description • Arquillian to start up your Java EE based projects • Moco or WireShark to startup a mock web service • HJSON is awesome!