logo

Mountebank Stubs with F#

The previous post described what Mountebank is, as well as how to use Mountebank imposters as Mocks via HTTP and F#.

This post describes how to use Mountebank imposters as Stubs via HTTP, xUnit.net, and F#.

Scenario

The Stub example in Mountebank API documentation simulates a RESTful endpoint that creates a Customer, the first time it’s called, and returns a 400 Bad Request the second time it’s called with the same Customer because the email address already exists.

Intercepting imposter Stub setup

When using Mountebank imposters as Stubs we typically care about configuring the Stub’s return values - the rest of the fixture setup and fixture teardown phases are always the same.

If fixture setup and fixture teardown phases are always the same, they could be extracted into an Interceptor class.

In xUnit.net we can do this by inheriting from BeforeAfterTestAttribute:

type UseImposterStubAttribute (mountebankHost, mountebankPort, imposterJson) =
    inherit BeforeAfterTestAttribute()

    override this.Before (methodUnderTest : MethodInfo) =
        Http.Request(
            UriBuilder(
                "http",
                mountebankHost,
                mountebankPort,
                "imposters/").ToString(),
            headers = [
              "Content-Type", HttpContentTypes.Json;
              "Accept"      , HttpContentTypes.Json ],
            httpMethod = "POST",
            body = TextRequest imposterJson)
        |> ignore

    override this.After (methodUnderTest : MethodInfo) =
        Http.Request(
            UriBuilder(
                "http",
                mountebankHost,
                mountebankPort,
                "imposters/" + ParsePortFrom(imposterJson)).ToString(),
            headers = [
                "Content-Type", HttpContentTypes.Json;
                "Accept"      , HttpContentTypes.Json ],
            httpMethod = "DELETE")
        |> ignore

Using imposter Stubs with xUnit.net

With xUnit.net and the UseImposterStubAttribute, the original Stub example can be written as:

[<Fact; UseImposterStub(
    "192.168.1.4",
    2525,
    @"
    {
      ""port"":4545,
      ""protocol"":""http"",
      ""stubs"":[
        {
          ""responses"":[
            {
              ""is"":{
                ""statusCode"":201,
                ""headers"":{
                  ""Location"":""http://localhost:4545/customers/123"",
                  ""Content-Type"":""application/xml""
                },
                ""body"":""<customer><email>customer@test.com</email></customer>""
              }
            },
            {
              ""is"":{
                ""statusCode"":400,
                ""headers"":{
                  ""Content-Type"":""application/xml""
                },
                ""body"":""<error>email already exists</error>""
              }
            }
          ]
        }
      ]
    }"
)>]
let createDuplicateCustomerThrows () =
    let expected = 201
    let mutable secondRequestHasFailed = false

    let actual =
        Create "<customer><email>customer@test.com</email></customer>"
    try
        Create "<customer><email>customer@test.com</email></customer>" |> ignore
    with
    | e -> if e.Message.Contains("email already exists")
           then secondRequestHasFailed <- true

    verify <@ actual.StatusCode = expected && secondRequestHasFailed @>

The UseImposterStubAttribute arguments are

When running the test, the output on the Mountebank server console is:

info: [mb:2525] POST /imposters/
info: [http:4545] Open for business...
info: [http:4545] 192.168.1.5:62897 => POST /imposters/
info: [http:4545] 192.168.1.5:62898 => POST /imposters/
info: [mb:2525] DELETE /imposters/4545

The important part is that the UseImposterStubAttribute removes the repetitive imposter setup and teardown steps from the actual test.

TCP Stubs

Brandon Byars, the creator of mountebank, provided an example of how to create TCP imposter Stubs with Java.

It is now easy to do something similar with F#, xUnit.net and the UseImposterStubAttribute:

[<Fact; UseImposterStub(
    "192.168.1.4",
    2525,
    @"
    {
      ""protocol"":""tcp"",
      ""port"":""4546"",
      ""mode"":""binary"",
      ""stubs"":[
        {
          ""responses"":[
            {
              ""is"":{
                ""data"": ""<encoded string>""
              }
            }
          ]
        }
      ]
    }"
)>]
let queryWithCancelledTravelPlan () =
    // F#-pseudocode conversion from the original Java sample.
    let expected = { travelPlan with Status = "Cancelled" }
    let actual = sut.Query("http://travelPlans/1234&date=2013-02-15");
    verify <@ actual .. expected @>

The complete source code is available on this gist - any comments or suggestions are always welcome.