Droidcon NYC 2017 - Advanced HTTP Mocking with WireMock

5701f31a8433a22ae736282de8d08cd6?s=47 Sam Edwards
September 25, 2017

Droidcon NYC 2017 - Advanced HTTP Mocking with WireMock

5701f31a8433a22ae736282de8d08cd6?s=128

Sam Edwards

September 25, 2017
Tweet

Transcript

  1. ADVANCED HTTP MOCKING WITH WIREMOCK Sam Edwards - @HandstandSam

  2. @HandstandSam #DCNYC17 WireMock runs as an HTTP Server.

  3. @HandstandSam #DCNYC17 Normally Android App HTTP Web Service remotehost.com

  4. @HandstandSam #DCNYC17 With WireMock Android App HTTP localhost:8080 remotehost:8080

  5. @HandstandSam #DCNYC17 Android App HTTP In App

  6. @HandstandSam #DCNYC17 Android App HTTP On Device In App

  7. @HandstandSam #DCNYC17 Android App HTTP On Device In App On

    Laptop
  8. @HandstandSam #DCNYC17 Android App HTTP On Device In App On

    Laptop Remote Server
  9. @HandstandSam #DCNYC17 Android App HTTP On Device In App On

    Laptop Remote Server localhost:8080
  10. @HandstandSam #DCNYC17 Android App HTTP On Device In App On

    Laptop Remote Server remotehost:8080
  11. @HandstandSam #DCNYC17 Started in 2011 by Tom Akehurst, Open Source,

    Apache 2.0
  12. @HandstandSam #DCNYC17 Presented at GTAC in November 2014 by Michael

    Bailey
  13. @HandstandSam #DCNYC17 Official Android Compatibility in January 2016

  14. @HandstandSam #DCNYC17 Droidcon NYC 2016

  15. @HandstandSam #DCNYC17

  16. @HandstandSam #DCNYC17 Continuous Integration & Cloud-Based Testing etc…

  17. @HandstandSam #DCNYC17 Deterministic HTTP == More Reliable Tests Not All

    Passing 100% Passed
  18. @HandstandSam #DCNYC17 Why Mock HTTP Calls? • Determinism & Reliability

    • Avoid Rate Limits and Server Costs • Avoid Modifying State • Edge Cases & Error Scenarios • Speed
  19. @HandstandSam #DCNYC17 Why Mock the HTTP Server? • Exercises your

    full networking stack. • Knowledge of the app’s inner-workings are not needed. • Swap your HTTP implementation without issues. • Use recorded HTTP responses to create representative test cases. • Test that your code survives in situations like 500 errors or slow-loading responses.
  20. @HandstandSam #DCNYC17 When should I use HTTP Mocking? • Android

    Tests! • Mock Flavor of App • Not in your normal Android builds
  21. @HandstandSam #DCNYC17 public class ErrorScenariosTest { @Rule public ActivityTestRule<HomeActivity> mActivityTestRule

    = new ActivityTestRule<>(HomeActivity.class); @Test public void http500ErrorTest() { stubFor(get(urlEqualTo("/category/Nintendo/items")) .willReturn(aResponse().withStatus(500))); onView(allOf(withId(R.id.categories), isDisplayed())) .perform(actionOnItemAtPosition(0, click())); onView(withText("Networking Error")).check(matches(isDisplayed())); } }
  22. @HandstandSam #DCNYC17 public class ErrorScenariosTest { @Rule public ActivityTestRule<HomeActivity> mActivityTestRule

    = new ActivityTestRule<>(HomeActivity.class); @Test public void http500ErrorTest() { stubFor(get(urlEqualTo("/category/Nintendo/items")) .willReturn(aResponse().withStatus(500))); onView(allOf(withId(R.id.categories), isDisplayed())) .perform(actionOnItemAtPosition(0, click())); onView(withText("Networking Error")).check(matches(isDisplayed())); } }
  23. @HandstandSam #DCNYC17 public class ErrorScenariosTest { @Rule public ActivityTestRule<HomeActivity> mActivityTestRule

    = new ActivityTestRule<>(HomeActivity.class); @Test public void http500ErrorTest() { stubFor(get(urlEqualTo("/category/Nintendo/items")) .willReturn(aResponse().withStatus(500))); onView(allOf(withId(R.id.categories), isDisplayed())) .perform(actionOnItemAtPosition(0, click())); onView(withText("Networking Error")).check(matches(isDisplayed())); } }
  24. @HandstandSam #DCNYC17

  25. @HandstandSam #DCNYC17 WHAT IS WIREMOCK?

  26. @HandstandSam #DCNYC17 Mapping GET - /hello - should return “Hello!”

  27. @HandstandSam #DCNYC17 WireMock Mappings HTTP GET - /hello - “Hello!”

    POST - /hi “hi.” Mappings ANY - /goodbye “Goodbye.” File-based REST API JVM
  28. @HandstandSam #DCNYC17 WireMock Mapping { "request": { "method": "GET", "urlPath":

    "/hello" }, "response": { "status": 200, “body": “Hello!”, "headers": { "Content-Type": "text/plain" } } }
  29. @HandstandSam #DCNYC17 Recording

  30. @HandstandSam #DCNYC17 Proxying Android App Web Service https://shopping-app.s3.amazonaws.com

  31. @HandstandSam #DCNYC17 Record File-based Mappings to Disk while Proxying Android

    App Web Service /__files /mappings https://shopping-app.s3.amazonaws.com
  32. @HandstandSam #DCNYC17 Record File-based Mappings to Disk Programmatic Java WireMockServer

    wireMockServer = new WireMockServer(wireMockConfig().withRootDirectory(getRootDirectory())); wireMockServer.enableRecordMappings(new SingleRootFileSource(getMappingDirectory()), new SingleRootFileSource(getFileDirectory())); wireMockServer.stubFor(any(urlMatching(“.*”)).willReturn(aResponse() .proxiedFrom(“https://shopping-app.s3.amazonaws.com”))); wireMockServer.start(); Command Line java -jar wiremock-standalone-2.8.0.jar \
 --proxy-all https://shopping-app.s3.amazonaws.com \
 --record-mappings
  33. @HandstandSam #DCNYC17 Demo: Android Recording

  34. @HandstandSam #DCNYC17 File-Based Recording & Playback on Android • Caveats:

    • Must configure to use External Storage/SD Card for __files and mappings • Recommendation (getting started): • Use Emulator & Run WireMock on Laptop • Connect Emulator to http://10.0.2.2:8080
  35. @HandstandSam #DCNYC17 Playback of File-Based Mappings on Android

  36. @HandstandSam #DCNYC17 Playback File-based Mappings Android App /__files /mappings HTTP

  37. @HandstandSam #DCNYC17 Playback File-based Mappings from Disk Programmatic Java WireMockServer

    wireMockServer = new WireMockServer(wireMockConfig().withRootDirectory(getRootDirectory())); wireMockServer.start(); Command Line java -jar wiremock-standalone-2.8.0.jar
  38. @HandstandSam #DCNYC17 Viewing Mappings • http://localhost:8080/__admin/ /__files /mappings

  39. @HandstandSam #DCNYC17 Demo: Android Playback

  40. @HandstandSam #DCNYC17 Stubbing

  41. @HandstandSam #DCNYC17 Terminology “Stubbing” is the act of creating a

    “Mapping” programmatically
  42. @HandstandSam #DCNYC17 Stubbing Options • File-based • JVM • REST

    API
  43. @HandstandSam #DCNYC17 Programmatic Stubbing via Java API stubFor(get(urlEqualTo(“/hello")) .willReturn(aResponse() .withHeader("Content-Type",

    "text/plain") .withBody("Hello world!")));
  44. @HandstandSam #DCNYC17 Programmatic Stubbing via Java API stubFor(get(urlEqualTo(“/hello")) .willReturn(aResponse() .withHeader("Content-Type",

    "text/plain") .withBody("Hello world!")));
  45. @HandstandSam #DCNYC17 Programmatic Stubbing via Java API stubFor(get(urlEqualTo(“/hello")) .willReturn(aResponse() .withHeader("Content-Type",

    "text/plain") .withBody("Hello world!")));
  46. @HandstandSam #DCNYC17 Programmatic Stubbing via Java API stubFor(get(urlEqualTo(“/hello")) .willReturn(aResponse() .withHeader("Content-Type",

    "text/plain") .withBody("Hello world!")));
  47. @HandstandSam #DCNYC17 Programmatic Stubbing via Java API stubFor(get(urlEqualTo(“/hello")) .willReturn(aResponse() .withHeader("Content-Type",

    "text/plain") .withBody("Hello world!")));
  48. @HandstandSam #DCNYC17 WireMock Mapping { "request": { "method": "GET", "urlPath":

    "/hello" }, "response": { "status": 200, “body": “Hello!”, "headers": { "Content-Type": "text/plain" } } }
  49. @HandstandSam #DCNYC17 Stubbing Options • File-based • JVM • REST

    API
  50. @HandstandSam #DCNYC17 Running WireMock Standalone JAR • Download Standalone JAR

    from Maven Central • java -jar wiremock-standalone-2.8.0.jar • --port • --https-port • http://localhost:8080/__admin/
  51. @HandstandSam #DCNYC17 Demo: Language Agnostic Stubbing via REST API

  52. @HandstandSam #DCNYC17 Running WireMock as a Proxy java -jar wiremock-standalone-2.8.0.jar

    \
 --proxy-all https://shopping-app.s3.amazonaws.com
  53. @HandstandSam #DCNYC17 Programmatic Stubbing via REST API curl --verbose \

    -X POST \ --data '{ "request": { "method": "GET", "urlPath": "/hello" }, "response": { "status": 200, "body": "Hello world!", "headers": { "Content-Type": "text/plain" } } }' \ 'http://localhost:8080/__admin/mappings'
  54. @HandstandSam #DCNYC17 Stubbing via REST API - String Body curl

    --verbose \ -X POST \ --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "status": 200, "body": "[{\"label\": \"?\"}]" } }' \ 'http://localhost:8080/__admin/mappings'
  55. @HandstandSam #DCNYC17 Stubbing via REST API - JSON Response curl

    --verbose \ -X POST \ --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "jsonBody": [{ "image": "https://shopping-app.s3.amazonaws.com/droidcon-nyc/droidcon-nyc.png", "label": "Droidcon NYC" }] } }' \ 'http://localhost:8080/__admin/mappings'
  56. @HandstandSam #DCNYC17 Stubbing via REST API - Binary Response curl

    --verbose \ -X POST \ --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "base64Body": "W3sibGFiZWwiOiAiQmFzZTY0IEVuY29kZWQifV0=" } }' \ 'http://localhost:8080/__admin/mappings'
  57. @HandstandSam #DCNYC17 Stubbing via REST API - 500 Error Response

    curl --verbose \ -X POST \ --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "status": 500 } }' \ 'http://localhost:8080/__admin/mappings'
  58. @HandstandSam #DCNYC17 https://chrome.google.com/webstore/detail/wiremock-extension/ikiaofdpbmofgmlhajfnhdjelkleljbl

  59. @HandstandSam #DCNYC17 Complex Stubbing

  60. @HandstandSam #DCNYC17 URL Pattern Stub curl --verbose \ -X POST

    \ --data '{ "request": { "method": "GET", "urlPathPattern": "/category/.*/items$" }, "response": { "jsonBody": [{ "image": "https://shopping-app.s3.amazonaws.com/droidcon-nyc/kittens.jpg", "label": "Kittens" }] } }' \ 'http://localhost:8080/__admin/mappings'
  61. @HandstandSam #DCNYC17 Query Parameter Stub curl --data '{ "request": {

    "method": "GET", "urlPath": "/protected", "queryParameters":{ "password": { "equalTo": "password" } } }, "response": { "body": "<h1>How did you know that my password was 'password'?</h1>" } }' \ 'http://localhost:8080/__admin/mappings'
  62. @HandstandSam #DCNYC17 Other Complex Stubbing • Header Matching (Serve Multiple

    API Versions) • Request Body Matching • Equal To • Contains • JSON Body • Doesn’t care about order of JSON elements
  63. @HandstandSam #DCNYC17 Simulating Errors and Latency

  64. @HandstandSam #DCNYC17 Pre-Defined Fault Types • EMPTY_RESPONSE • Return a

    completely empty response. • MALFORMED_RESPONSE_CHUNK • Send an OK status header, then garbage, then close the connection. • RANDOM_DATA_THEN_CLOSE • Send garbage then close the connection.
  65. @HandstandSam #DCNYC17 Simulating Faults curl --verbose \ -X POST \

    --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "fault": "MALFORMED_RESPONSE_CHUNK" } }' \ 'http://localhost:8080/__admin/mappings'
  66. @HandstandSam #DCNYC17 Stub Specific Fixed Delay curl --verbose \ -X

    POST \ --data '{ "request": { "method": "GET", "url": "/category/Nintendo/items" }, "response": { "body": "[{\"label\": \"Better Late Than Never\"}]", "fixedDelayMilliseconds": "2000" } }' \ 'http://localhost:8080/__admin/mappings'
  67. @HandstandSam #DCNYC17 Delay Types • Stub-specific Delays • Global Delays

    • Static Delays • Random Delays
  68. @HandstandSam #DCNYC17 Stateful Behavior with Scenarios

  69. @HandstandSam #DCNYC17 1. One Stateful Behavior with Scenarios Shopping List

    Scenario States GET /items Required: “Started” New State: “Two Items” 1. One 2. Two GET /items Required: “Two Items” New State: “Three Items” 1. One 2. Two 3. Three GET /items Required: “Three Items” New State: “Started”
  70. @HandstandSam #DCNYC17 Verifying Networking Calls

  71. @HandstandSam #DCNYC17 Verifying Network Calls (Mockito-like Syntax) • Verify Called

    Once
 
 verify(getRequestedFor(urlEqualTo(“/category/Nintendo/items”))); • Verify Called 3 Times
 
 verify(3, getRequestedFor(urlEqualTo(“/category/Nintendo/items”))); • Full Request Log
 
 List<ServeEvent> allServeEvents = wireMockServer.getAllServeEvents();
  72. @HandstandSam #DCNYC17 Programmatic Resets • wireMockServer.resetMappings(); • wireMockServer.resetScenarios();

  73. @HandstandSam #DCNYC17 What Kind of Stubbing Should I Use? File

    Based vs. Programmatic
  74. @HandstandSam #DCNYC17 Programmatic Mocks > File-based Mocks • Save time

    by avoiding time consuming record/playback cycle. • Generate time-sensitive data (yesterday, etc). • Iterate through models to generate multiple ID specific mappings. • Hold a programmatic model of expected results for assertions. • Again, save a LOT of time after a small-ish upfront investment.
  75. @HandstandSam #DCNYC17 File-based Mocks > Programmatic Mocks • Recording •

    Getting Started • Simple Cases • Working with Non-Developers
  76. @HandstandSam #DCNYC17 Android Caveats

  77. @HandstandSam #DCNYC17 WireMock on Android - Dependencies dependencies { androidTestCompile(“com.github.tomakehurst:wiremock:2.8.0")

    { exclude group: 'org.apache.httpcomponents', module: 'httpclient' exclude group: 'org.json', module: 'json' exclude group: 'org.ow2.asm', module: 'asm' } androidTestCompile 'org.apache.httpcomponents:httpclient-android:4.3.5.1' }
  78. @HandstandSam #DCNYC17

  79. @HandstandSam #DCNYC17

  80. @HandstandSam #DCNYC17 WireMock on Android - Project Config Recommended, but

    not required: android { defaultConfig { minSdkVersion 21 multiDexEnabled true }
 }
  81. @HandstandSam #DCNYC17 Final Thoughts

  82. @HandstandSam #DCNYC17 Recommendations • Play with WireMock as a desktop/server

    tool immediately. It’s awesome. • Evaluate other libraries. Don’t use WireMock in your app until you’ve looked at other options. • Mock Web Server - HTTP Server • Mock Web Server + RESTMock - More powerful • OkReplay - OkHttp Interceptor + File Based Recording • Retrofit - Behavior Delegate • Mockito • 70,000 methods??? - Multidex and get over it
  83. @HandstandSam #DCNYC17 Don’t carry a Swiss-Army Knife if you only

    need a knife.
  84. @HandstandSam #DCNYC17 THANKS! https://github.com/handstandsam/ShoppingApp