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

Testing RESTful services: The What, The How and The Automated

Testing RESTful services: The What, The How and The Automated

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

April 25, 2016
Tweet

More Decks by Alexey Buzdin

Other Decks in Programming

Transcript

  1. Why API tests? • Tests routing • Tests serialization/deserialization •

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

    ServletResponse response) throws ServletException, IOException { // do something in here } } Simple Servlet
  3. Simple Servlet @Controller @RequestMapping("/") public class SimpleController { @RequestMapping(method =

    RequestMethod.GET) public String get() { return “Hello”; } } (Annotation Driven Development … he.. he..)
  4. Why API tests? • Tests routing • Tests serialization/deserialization •

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

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

    Tests server response • Can be written by a QA who does not knows the code
  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. Hint: Decouple integrations from your app Design your integrations to

    be easily pluggable and mockable in advance, or feel the consequences
  9. Moco https://github.com/dreamhead/moco java -jar moco-runner.jar http -p 12306 -c foo.json

    { "request": { "json": { "foo": “bar" } }, "response": { "text": “foo" } }
  10. 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
  11. 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
  12. 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
  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. @RunWith(Arquillian.class) public class GreeterTest { @Deployment public static WebArchive.class createDeployment()

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

    given().port(8888). when().get("/healthcheck"). then().statusCode(200); } https://github.com/jayway/rest-assured
  16. 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")
  17. @Test public void applicationIsUp() { given().port(8888). when().get("/healthcheck"). then().statusCode(200); } Scenario:

    Application is up Given baseUri is http://localhost:8888/ When GET request on /healthcheck Then status code is 200 https://github.com/ctco/cukes-rest
  18. 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
  19. Scenario: Should accept a cute RoboGirl Given request body: """

    name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """ When POST request on /robots Then status code is 201 HJson / HumanJSON
  20. Scenario: Should accept a cute RoboGirl Given request body: """

    name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """ When POST request on /robots Then status code is 201 And header Location ends with "/robots/Tay.ai"
  21. Accept tested • Application is started • Resource collection exists

    • Provided JSON is parsed successfully • No validation errors
  22. Scenario: Should create a new Robot … When POST request

    on /robots Then status code is 201 And header Location ends with “/robots/Tay.ai" When GET request on {(header.Location)} Then status code is 200
  23. Created tested • Application is started • Resource collection exists

    • Provided JSON is parsed successfully • No validation errors • Resource was stored on the server
  24. What did we miss? Scenario: ….. Given request body: """

    name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """ When POST request on /robots
  25. Test Actual Data …. When GET request on /robots/Tay.ai Then

    status code is 200 And response equals to: """ name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """
  26. Unicode is not working java.lang.AssertionError: 1 expectation failed. Response body

    doesn't match expectation. Expected: "{\"name\" : \"Tay.ai\", \"personality\" : { \"name\": \"Tãy\",\"race\" : \"Hispanic\", \"age\" : 20}}" Actual: {"name" : "Tay.ai", "personality" : { "name" : "T y","race" : "Hispanic","age" : 20}}
  27. Test Actual Data …. When GET request on /robots/Tay.ai Then

    status code is 200 And response equals to: """ name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """
  28. 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
  29. Hint: Use JSONPath and XPath to assert equality When GET

    request on /robots/Tay.ai Then status code is 200 And response contains properties: """ name : Tay.ai personality : { name : Tãy race : Hispanic age : 20 } """ name = Tay.ai personality.name = Tãy personality.race = Hispanic personality.age = 20
  30. JSONPath and Arrays name : Tay.ai personality : { name

    : Tãy race : Hispanic age : 20 } skills: [ "driving", "hacking", "translating" ] name = Tay.ai personality.name = Tãy personality.race = Hispanic personality.age = 20 skills[0] = driving skills[1] = hacking skills[2] = translating
  31. Hint: Arrays are unordered if fetched straight from DB Either

    implement order by or find the correct index programmatically
  32. Hint: Always prepare test data yourself You don’t want to

    have race conditions or any other unexpected trouble born from shared state.
  33. 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
  34. 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)
  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. • 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
  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. Hint: Expose all errors and conditions to the client in

    RESTful way! REST is statless; be statless with errors as well!
  40. More GET on a resource • Get existing resource ->

    OK (200) • Get non existing resource -> Not Found (404) • Unsupported Media Type (415)
  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. DELETE on a resource • Delete non existing resource ->

    Not Found (404) • Delete existing resource -> OK (200) • Resource is in use -> Forbidden (403)
  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. 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)
  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. Hint: Invest into describing your API Either it be a

    API Doc or better an API representation in some data format
  52. 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 } } }
  53. Asynchronous Scenario: ….. …. When POST request on /robots Then

    status code is 202 And header Location contains "/robots/Tay.ai" ….
  54. Other REST-assured features • form • basic • certificate •

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

    Multi-value headers and cookies • Multi-part form data support • Filters
  56. 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
  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 • 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
  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/XPath to assert the results
  64. Tools to look at • REST-assured or cukes-rest 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 to startup a mock web service • HJSON is awesome!