After we saw how easy it is, with DieselApps to create and test simple microservices, let's look at more advanced simulation.
Let's take a made-up simple card payment processor example... with these functions:
$mock cards.authorize(cardno, amount) => (success="true", code="1234")
$mock cards.settle(code) => (success="true")
$mock cards.void(code) => (success="true")
It's fairly straight forward: you can authorize a card payment, void it or settle it... but how can we simulate different problems common in testing microservices: timeouts, erorr codes etc? We can use different magic amounts:
$mock cards.authorize(cardno, amount)
=> $if (amount is "1.00") (success="true", code="1234")
=> $if (amount is "2.00") (success="false")
=> $if (amount is "3.00") (success="false")
$mock cards.settle(code) => (success="true")
$mock cards.void(code) => (success="true")
At this point, our new microservice is already mocked - test it here:
$send cards.authorize (cardno="11111111", amount="1.00")
$expect (success is "true")
If you click on "Trace" you will see that this mock meets the expectation and you can already invoke it, with the links from the REST tab.
~=
(aka matches) operator, it will only fire for "Jane"
and no other name. If you had used the ?=
(aka a sample), then it would fire for any name. You can try this by clicking on the first REST link and replacing Jane
with Fufu
and you'll notice that you no longer get a result, as the rule no longer fires (to force the tests to run again, click on the Trace tab again in the story section).
This is now already alive and running, since it is self-contained (it's a mock and an expectation / test)... this is a story. We'd normally say that it's a use case or a story describing a behaviour or functionality of the system.
Go ahead, click on the REST tab and use the links to see it run.
Is this interesting? ...
Here is an example of a full blown implementation for a 3 method API for a "blog" service, in Lagom and Java - take a look at it for a couple of minutes.
That work comes after setting up an sbt project (easier to setup if you use the activator templates) as well as having a JVM, scala etc... but let's see what we can do here, in about 1 minute... maybe this three-liner is enough to prototype things:
$mock blog.addPost(body) => (id=123)
$mock blog.getPost(id) => (post="This is a blog post id="+id)
$mock blog.updatePost(id, body) => (post=body+id)
There, it's mocked and already up and running. Let's define some tests for it now... another 2 minutes:
$send blog.addPost (body="This is a blog post")
$expect (id is Number)
$send blog.getPost (id="3")
$expect (post contains "3")
$send blog.updatePost (id="3",body="new body")
$expect (post contains "new")
Done. We have very clear expectations, which have already become tests, as well as simple but explicit mocks of a full component.
Click on the "Trace" tab and notice the green lights as the tests are successful (since they run against the mocks just above).
It's true - the bigger example above, the full Lagom implementation also persists the posts... while we do not - we just simulate them, so one could argue that we don't have as complete of a solution. Let's fix that, using a simple in-memory store (it would look the same with a proper store):
$mock blog2.addPost(body) => diesel.memdb.upsert (collection="Posts", document=body)
$mock blog2.getPost(id) => diesel.memdb.get(collection="Posts", id)
$mock blog2.updatePost(id, body) => diesel.memdb.upsert (collection="Posts", id=id, document=body)
Yup! Connecting to a database can be that easy! This is just an in-memory mock (wouldn't use a proper database, to be filled by bad crawlers, would we?).
And now that we have the new API up and running, backed by an actual data store, let's re-do the tests again:
$send blog2.addPost (id="3", body="This is a blog post")
$expect (id ~= "[0-9a-z]+")
$send blog2.getPost (id)
$expect (document contains "post")
$send blog2.updatePost (id,body="new body")
$send blog2.getPost (id)
$expect (document contains "new")
$send diesel.memdb.clear
id
is now allocated by the database, we had to change the tests slightly, to reuse the id
, instead of specifying it - a better designed test would have reused it upfront and then it would apply to the mock or the full implementation - just a thought.
You noticed that the three separate sets of fiddles are entangled in pairs: a "spec" and a "story". Each story has its own spec and changing one spec will impact its entangled story only and does not affect the others.
This way, you can have multiple fiddles in the same page and tell multiple stories, without these interfering with each-other. Think of them as groups or modules or whatever other conceptualization you fancy for entangling... things.
So far we used stand-alone inline fiddles - fully active and everything, but they look very much like dry code - not something an analyst would perhaps care to look at... here's how to create actual stories containing the same tests. The test DSL looks the same as the fiddle (you can see it in the source of this page), but the result is rendered differently, hiding the code-like unpleasantries. Here's the first case presented above, as part of a story rather than just fiddles.
0 $mock::
say.hi (name ~= "Jane"
)
. (greeting
=("Hello, " + name)
)
send::
msg say.hi (name="Jane"
)
expect:: (greeting contains "Jane"
)
More on stories, next time. We will also look next at mocking entire systems, how to deal with JSON and complex REST APIs, in the meantime, checkout the minimally minimalistic lean microservices we're creating at dieselapps.com.
More resources:
Read this intro to Lagom - it's a good microservices platform for Java.