Mountebank Mocks with F#

This post describes how to use Mountebank imposters as Mock Objects/Test Spies via HTTP with F#.

As a learning exercise, for mountebank, but also for F#, I wrote a few F# functions to communicate with Mountebank imposters via HTTP and a few passing tests. The rest of the post describes what I learned so far.

What is Mountebank?

Mountebank is a tool (and currently the only one) which provides multi-protocol, multi-language, on-demand, Test Doubles over the wire, named imposters.

Now we can send a mail message via SMTP to the Mountebank imposter using port 4547:

From: "Customer Service" <code@nikosbaxevanis.com>
To: "Customer" <nikos.baxevanis@gmail.com>
Subject: Thank you for your order
Hello Customer,
Thank you for your order from company.com. Your order will
be shipped shortly.
Your friendly customer service department.

In F# this can be written as:

letexpectedSubject="Thank you for your order"(newSmtpClient("192.168.1.3",4547)).Send(newMailMessage("code@nikosbaxevanis.com","nikos.baxevanis@gmail.com",expectedSubject,"Hello Customer, Thank you for your order from company.com."))

To get the captured requests from the imposter we can issue a GET or a DELETE request. Normally, this happens during the fixture teardown phase via DELETE:

According to Hypertext Transfer Protocol -- HTTP/1.1 for HTTP method definitions, it is OK to issue a DELETE to also get the captured requests from the imposter: *"A successful response SHOULD be 200 (OK) if the response includes an entity describing the status" -- Hypertext Transfer Protocol -- HTTP/1.1, 9.7 DELETE*.

letverify=Swensen.Unquote.Assertions.test[<Fact>]letsendMailTransmitsCorrectSubject()=letexpectedSubject="Thank you for your order!"letmountebankHost="192.168.1.3"letimposterPort=4547letspy=Imposter.Create"smtp"mountebankHostimposterPort(newSmtpClient(mountebankHost,imposterPort)).Send(newMailMessage("code@nikosbaxevanis.com","nikos.baxevanis@gmail.com",expectedSubject,"Hello Customer, Thank you for your order from company.com."))verify<@matchSmtpSpy.GetCapturedRequestspy"subject"with|Someactual->expectedSubject=actual|None->raise<|InvalidOperationException("No property named 'subject' found in captured requests.")@>

The Mountebank website uses the Mock Object terminology when verifying indirect outputs. However, the examples shown here don't setup expectations - instead they only verify *captured*-indirect outputs of the SUT; thus the Test Spy terminology is used in code.

Scenario: SMTP client transmits correct number of requests.

In this case, it’s only necessary to verify that the SMTP request on the imposter was made only once:

[<Fact>]letsendMailTransmitsCorrectNumberOfSmtpRequests()=letexpectedNumberOfRequests=1letmountebankHost="192.168.1.3"letimposterPort=4546letspy=Imposter.Create"smtp"mountebankHostimposterPort(newSmtpClient(mountebankHost,imposterPort)).Send(newMailMessage("code@nikosbaxevanis.com","nikos.baxevanis@gmail.com","Thank you for your order!","Hello Customer, Thank you for your order from company.com."))verify<@matchImposter.GetCapturedRequestsspywith|Someactual->expectedNumberOfRequests=(actual|>Seq.length)|None->raise<|InvalidOperationException(sprintf"Expected %i calls but received none."expectedNumberOfRequests)@>

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