ServiceMock is a Ruby gem that provides a wrapper over the WireMock library. The gem provides the ability to perform many actions on a WireMock instance including starting, stopping, and several ways to stub out calls. It is highly recommended that you take the time to read the WireMock documentation and understand the how the library works.
This gem can be used to help you virtualize services for your tests. For example, if you are unable to achieve effective test data management with the backend systems you can use this gem (with WireMock) to simulate (or mock) the services while controlling the data that is returned.
ServiceMock also provides two built-in rake task that can be used to start and stop an instance of WireMock. The rake task need to be executed on the machine that the server will run - there is no ability for remote start and stop.
This section is a high-level overview of how I typically use this gem in my projects. I will skip over some details here but you can find them below.
Let's say that I have an application that is using many restful services. I would begin by starting up WireMock in proxy mode and make my application request the services from the WireMock url (usually with a Rake task I would create). WireMock will create two files for each service call - one that has the request / response structure and one that has the response body.
Next I would take the files created by WireMock and turn them into my templates. I do
this by updating values in the files to data elements. For example, if I have some place
that had a persons last name I would put <%= last_name %>
there. If I wanted to provide
a default that could be used I would use one of this gems ERB helper methods that allows
me to provide a default - <%= value_with_default(last_name, 'Smith') %>
. If there
is part of the message that repeats itself I might create a loop. I can use any Ruby
code I want.
Once I have the templates created I then turn to my tests. For each test I specify the data that I need to merge with the templates or override the defaults. I put this data in my yml files. Once I have the data file created I simply make the call in my code to stub out the services.
All interaction with the WireMock services is via calls to an instance of
ServiceMock::Server
. On your local machine you can start
and stop
an
instance of the server process. On local and remote machines you can also
use one of several methods to create stubs (specify request/response data)
as well as tell the server to save
in-memory stubs, reset_mappings
to
remove all of the stubs you have provided and reset_all
which completely
clears all mappings and files that you have setup for WireMock to return
as your system under test interacts with it.
When you create an instance of ServiceMock::Server
you often need to provide
some additional information. The required piece of data is the version of WireMock
you will be using. If the name of the WireMock jar file you will be using
is wiremock-standalone-2.5.0.jar
then the version you should provide
is standalone-2.5.0
. In other words, take off the initial wiremock-
and the trailing .jar
and this is your version. The other optional value
you can provide is the working directory - the location where the WireMock
jar is located. By default the working directory is set to config/mocks
.
You will initialize the server like this:
# uses default working directory
my_server = ServiceMock::Server.new('standalone-2.0.10-beta')
# or this sets the working directory
my_server = ServiceMock::Server.new('standalone-2.0.10-beta', '/path/to/jar')
There are two additional values (inherit_io and wait_for_process) that
have default values of false
. If set to true
, inherit_io
will cause the
server instance to 'inherit' the standard out and in for the running WireMock
process. When wait_for_process
is set to true
it will cause the
call to start
to block until the underlying WireMock process exits.
These values can be overwritten in the call to start
as described below.
You start the server by calling the start
method but it doesn't end there.
There are a large number of parameters you can set to control the way that
WireMock runs. These are set via a block that is passed to the start
method.
my_server.start do |server|
server.port = 8081
server.record_mappings = true
server.root_dir = '/path/to/root'
server.verbose = true
end
The values that can be set are:
value | description |
---|---|
port | The port to listen on for http request |
https_port | The port to listen on for https request |
https_keystore | Path to the keystore file containing an SSL certificate to use with https |
keystore_password | Password to the keystore if something other than "password" |
https_truststore | Path to a keystore file containing client certificates |
truststore_password | Optional password to the trust store. Defaults to "password" if not specified |
https_reuire_client_cert | Force clients to authenticate with a client certificate |
verbose | print verbose output from the running process. Values are true or false |
root_dir | Sets the root directory under which mappings and __files reside. This defaults to the current directory |
record_mappings | Record incoming requests as stub mappings |
match_headers | When in record mode, capture request headers with the keys specified |
proxy_all | proxy all requests through to another URL. Typically used in conjunction with record_mappings such that a session on another service can be recorded |
preserve_host_header | When in proxy mode, it passes the Host header as it comes from the client through to the proxied service |
proxy_via | When proxying requests, route via another proxy server. Useful when inside a corporate network that only permits internet access via an opaque proxy |
enable_browser_proxy | Run as a browser proxy |
no_request_journal | Disable the request journal, which records incoming requests for later verification |
max_request_journal_entries | Sets maximum number of entries in request journal. When this limit is reached oldest entries will be discarded |
In addition, as mentioned before, you can set the inherit_io
and wait_for_process
options
to true
inside of the block.
Please read the WireMock stubbing documentation so you understand the message structures that are used. It is very important that you understand this first. Those details will not be covered here.
There are three methods that can be used to stub services.
my_server.stub(string_containing_json_stub)
my_server.stub_with_file(file_containing_json_stub)
my_server.stub_with_erb(erbfile_containing_json_stub, hash_with_values)
The first method is pretty self-explanatory. You simply pass a string that contains the json stub and the server contacts the running server process and sets it up. The second method takes a full path to a file that contains the json stub.
The third method is where things get interesting. It allows you to provide an ERB file that will become your json stub. ERB stands for Embedded Ruby and allows you to insert Ruby code inside any type of file - including json. Using this ability allows us to build templates of the service messages. It is quite common to mock a service many times in your test suite and while the structure is the same with each mock the data supplied and returned is different. Using templates allows us to simply write the template once and then supply the data for each mock.
The third method takes the full path to an erb file that contains the
json stub and a second parameter that is a Hash
which provides the
data that is used to fill in the data elements in the template.
If you set the port value when you started the server you will need to do the same via a block when stubbing.
my_server.stub(string_containing_json_stub) do |server|
server.port = 8081
end
If the WireMock instance is running on another server you will need to
provide the url so it can be found. This is often necessary when you
are running your tests in a build pipeline or a continuous integration
server. You can do this by setting the remote_host
value like this:
my_server.stub_with_erb(erbfile, values) do |server|
server.remote_host = 'other_host_name'
end
Another way to set both the port and remote host is to set an instance
variable WIREMOCK_URL
. It should take the form 'http://hostname:port'.
It is often necessary to stub multiple service calls in order to
complete a test. ServiceMock has created a simple way to do this.
It is implemented in a class named ServiceMock::StubCreator
.
This class has a single public method create_stubs_with
which
takes the name of the name of a file that has the data for all
of the stubs you wish to create and optional data to merge with
that file. Also, when you create an instance of the class it
requires you to pass an instance of ServiceMock::Server
.
At this time you need to setup everything in a specific directory
structure. The directory it looks for is config/mocks/stubs
.
Inside that directory it looks for two additional directories -
data
and templates
. The data file needed by the call needs
be located in the data
directory and the ERB
files (templates)
that it references needs to be in the templates
directory.
The structure of the data file drives the stubbing. Let's start with an example.
service1.erb:
first_name: Mickey
last_name: Mouse
service2.erb:
username: mmouse
password: secret
With the above file the method call will mock two services. It will first
of all read the file service1.erb
from the templates
directory and
stub it passing the data that is associated. Next it will read the next
template file (service2.erb
), etc.
The create_stubs_with
method has an optional second parameter. It is
a Hash
that can be used to override a value that is contained within
our data file. For example, if I wanted to override just the password
used in service2 from the example above I do this:
stub_creater.create_stubs_with(filename, {'service2' => {'password' => 'updated'}})
There might be some tests that you want to run sometimes against mocked
services and sometimes against the real services. This is very simple
to do. If you simply set the value of ServieMock.disable_stubs
to
true all of the stub_*
methods return immediately without stubbing
anything. In practice you would simply point your application to the
real services and disable the stubs via this flag.
There are additional methods that provide access to the running WireMock server. If you changed the port during startup then you will need to do so here as well. The methods are:
my_server.stop
Stops the running WireMock server if it is running locally.
my_server.save
Writes all of the stubs to disk so they are available the next time the server starts.
my_server.reset_mappings
Removes the stubs that have been created since WireMock was started.
my_server.reset_all
Removes all stubs, file based stubs, and request logs from the WireMock server.
There are two methods that can be used in your ERB templates. They are:
render(path)
This will read the file at path
and place its' contents in place of the call.
I use this to insert the body inside the response.
value_with_default(value, default)
This call allows you to provide a default for the data that will be inserted into
the ERB template. It can be used like this -
<%= value_with_default(city, 'Toronto') %>
There are two rake tasks that are provided to make it easy to start and
stop the WireMock server when working locally. You can simply add
the following to your Rakefile
:
require 'service_mock'
require 'service_mock/rake/rake_tasks'
WIREMOCK_VERSION = 'standalone-2.0.10-beta'
ServiceMock::Rake::StartServerTask.new(:start_server, WIREMOCK_VERSION)
ServiceMock::Rake::StopServerTask.new(:stop_server, WIREMOCK_VERSION)
Any parameter that can be passed to the start
method can also be passed
to the rake task.
ServiceMock::Rake::StartServerTask.new(:start_server, WIREMOCK_VERSION) do |server|
server.port = 8081
server.record_mappings = true
server.root_dir = '/path/to/root'
server.verbose = true
end
If you set the port in the start task then you will also need to set it in the stop task.
ServiceMock::Rake::StopServerTask.new(:stop_server, WIREMOCK_VERSION) do |server|
server.port = 8081
end
Add this line to your application's Gemfile:
gem 'service_mock'
And then execute:
$ bundle
Or install it yourself as:
$ gem install service_mock
After checking out the repo, run bin/setup
to install dependencies.
Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
Bug reports and pull requests are welcome on GitHub at https://github.com/cheezy/service_mock. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.