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

Droidcon NYC 2017 - Advanced HTTP Mocking with WireMock

Sam Edwards
September 25, 2017

Droidcon NYC 2017 - Advanced HTTP Mocking with WireMock

Sam Edwards

September 25, 2017
Tweet

More Decks by Sam Edwards

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. @HandstandSam
    #DCNYC17
    Android App HTTP
    In App

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. @HandstandSam
    #DCNYC17
    Started in 2011 by Tom Akehurst, Open Source, Apache 2.0

    View Slide

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

    View Slide

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

    View Slide

  14. @HandstandSam
    #DCNYC17
    Droidcon NYC 2016

    View Slide

  15. @HandstandSam
    #DCNYC17

    View Slide

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

    View Slide

  17. @HandstandSam
    #DCNYC17
    Deterministic HTTP == More Reliable Tests
    Not All Passing 100% Passed

    View Slide

  18. @HandstandSam
    #DCNYC17
    Why Mock HTTP Calls?
    • Determinism & Reliability
    • Avoid Rate Limits and Server Costs
    • Avoid Modifying State
    • Edge Cases & Error Scenarios
    • Speed

    View Slide

  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.

    View Slide

  20. @HandstandSam
    #DCNYC17
    When should I use HTTP Mocking?
    • Android Tests!
    • Mock Flavor of App
    • Not in your normal Android builds

    View Slide

  21. @HandstandSam
    #DCNYC17
    public class ErrorScenariosTest {
    @Rule
    public ActivityTestRule 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()));
    }
    }

    View Slide

  22. @HandstandSam
    #DCNYC17
    public class ErrorScenariosTest {
    @Rule
    public ActivityTestRule 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()));
    }
    }

    View Slide

  23. @HandstandSam
    #DCNYC17
    public class ErrorScenariosTest {
    @Rule
    public ActivityTestRule 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()));
    }
    }

    View Slide

  24. @HandstandSam
    #DCNYC17

    View Slide

  25. @HandstandSam
    #DCNYC17
    WHAT IS WIREMOCK?

    View Slide

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

    View Slide

  27. @HandstandSam
    #DCNYC17
    WireMock Mappings
    HTTP GET - /hello - “Hello!”
    POST - /hi “hi.”
    Mappings
    ANY - /goodbye “Goodbye.”
    File-based REST API JVM

    View Slide

  28. @HandstandSam
    #DCNYC17
    WireMock Mapping
    {
    "request": {
    "method": "GET",
    "urlPath": "/hello"
    },
    "response": {
    "status": 200,
    “body": “Hello!”,
    "headers": {
    "Content-Type": "text/plain"
    }
    }
    }

    View Slide

  29. @HandstandSam
    #DCNYC17
    Recording

    View Slide

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

    View Slide

  31. @HandstandSam
    #DCNYC17
    Record File-based Mappings to Disk while Proxying
    Android App Web Service
    /__files /mappings
    https://shopping-app.s3.amazonaws.com

    View Slide

  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

    View Slide

  33. @HandstandSam
    #DCNYC17
    Demo:
    Android Recording

    View Slide

  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

    View Slide

  35. @HandstandSam
    #DCNYC17
    Playback
    of File-Based Mappings
    on Android

    View Slide

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

    View Slide

  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

    View Slide

  38. @HandstandSam
    #DCNYC17
    Viewing Mappings
    • http://localhost:8080/__admin/
    /__files /mappings

    View Slide

  39. @HandstandSam
    #DCNYC17
    Demo:
    Android Playback

    View Slide

  40. @HandstandSam
    #DCNYC17
    Stubbing

    View Slide

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

    View Slide

  42. @HandstandSam
    #DCNYC17
    Stubbing Options
    ● File-based
    ● JVM
    ● REST API

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  48. @HandstandSam
    #DCNYC17
    WireMock Mapping
    {
    "request": {
    "method": "GET",
    "urlPath": "/hello"
    },
    "response": {
    "status": 200,
    “body": “Hello!”,
    "headers": {
    "Content-Type": "text/plain"
    }
    }
    }

    View Slide

  49. @HandstandSam
    #DCNYC17
    Stubbing Options
    ● File-based
    ● JVM
    ● REST API

    View Slide

  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/

    View Slide

  51. @HandstandSam
    #DCNYC17
    Demo:
    Language Agnostic Stubbing
    via REST API

    View Slide

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

    --proxy-all https://shopping-app.s3.amazonaws.com

    View Slide

  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'

    View Slide

  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'

    View Slide

  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'

    View Slide

  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'

    View Slide

  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'

    View Slide

  58. @HandstandSam
    #DCNYC17
    https://chrome.google.com/webstore/detail/wiremock-extension/ikiaofdpbmofgmlhajfnhdjelkleljbl

    View Slide

  59. @HandstandSam
    #DCNYC17
    Complex Stubbing

    View Slide

  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'

    View Slide

  61. @HandstandSam
    #DCNYC17
    Query Parameter Stub
    curl --data '{
    "request": {
    "method": "GET",
    "urlPath": "/protected",
    "queryParameters":{
    "password": {
    "equalTo": "password"
    }
    }
    },
    "response": {
    "body": "How did you know that my password was 'password'?"
    }
    }' \
    'http://localhost:8080/__admin/mappings'

    View Slide

  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

    View Slide

  63. @HandstandSam
    #DCNYC17
    Simulating Errors and
    Latency

    View Slide

  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.

    View Slide

  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'

    View Slide

  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'

    View Slide

  67. @HandstandSam
    #DCNYC17
    Delay Types
    • Stub-specific Delays
    • Global Delays
    • Static Delays
    • Random Delays

    View Slide

  68. @HandstandSam
    #DCNYC17
    Stateful Behavior with
    Scenarios

    View Slide

  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”

    View Slide

  70. @HandstandSam
    #DCNYC17
    Verifying Networking Calls

    View Slide

  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 allServeEvents = wireMockServer.getAllServeEvents();

    View Slide

  72. @HandstandSam
    #DCNYC17
    Programmatic Resets
    • wireMockServer.resetMappings();
    • wireMockServer.resetScenarios();

    View Slide

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

    View Slide

  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.

    View Slide

  75. @HandstandSam
    #DCNYC17
    File-based Mocks > Programmatic Mocks
    ● Recording
    ● Getting Started
    ● Simple Cases
    ● Working with Non-Developers

    View Slide

  76. @HandstandSam
    #DCNYC17
    Android Caveats

    View Slide

  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'
    }

    View Slide

  78. @HandstandSam
    #DCNYC17

    View Slide

  79. @HandstandSam
    #DCNYC17

    View Slide

  80. @HandstandSam
    #DCNYC17
    WireMock on Android - Project Config
    Recommended, but not required:
    android {
    defaultConfig {
    minSdkVersion 21
    multiDexEnabled true
    }

    }

    View Slide

  81. @HandstandSam
    #DCNYC17
    Final Thoughts

    View Slide

  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

    View Slide

  83. @HandstandSam
    #DCNYC17
    Don’t carry a Swiss-Army
    Knife if you only need a
    knife.

    View Slide

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

    View Slide