Hoodoo Guides: Fundamentals

Purpose

This guide gives an introduction to basic Hoodoo API concepts, shows how to write a “hello world” service without a persistence back-end and describes template service shell.

Although these Guides are believed accurate at the time of writing, always remember that the Hoodoo RDoc information is the source of truth for Hoodoo’s public API.

Concepts

Nomenclature

Hoodoo provides a Ruby programming framework which describes various entities:

Actions

Hoodoo supports exactly 5 actions, mapped by HTTP verb:

The use of the verb PATCH indicates that both partial or full resource updates are supported. Omission of a field in an inbound payload means “do not change”.

At a minimum, a resource implementation consists of between one and five instance methods of a Hoodoo::Services::Implementation subclass, one for each supported action. All have identical input parameter signatures but differing requirements for their side effects.

Hoodoo responds to CORS requests via the OPTIONS verb automatically, with no code required by service authors.

Routing

The interface for a resource names that resource and declares where its endpoint is, in terms of a URI; this is the only sort of routing you get in Hoodoo. Each interface is versioned, with a default version of 1.

Content types

Hoodoo requires the caller to specify the content type and encoding of inbound data. At the time of writing only JSON and UTF-8 are supported. Every Hoodoo inbound API call must include a Content-Type HTTP header with a value of application/json; charset=utf-8. Without this, Hoodoo will reject the call. Support for other request/response content types may be added in future.

Error reporting

Hoodoo reports any error via a non-2xx HTTP response code and representation of an Errors JSON resource. This is described in the Hoodoo API Specification – basically it is a valid resource representation and contains details of one or more errors that occurred during processing. For example:

{
  "id": "a73760e4458946519d18beeddb7c781d",
  "kind": "Errors",
  "created_at": "2015-08-04T01:20:53Z",
  "interaction_id": "fc258127cd354115ad77dc6a4b6470c3",
  "errors": [
    {
      "code": "platform.malformed",
      "message": "Content-Type 'application/xml; charset=utf-8' does not match supported types '[\"application/json\"]' and/or encodings '[\"utf-8\"]'"
    }
  ]
}

Any exception in service code is caught and reported back via an error instance as shown above. Backtraces are included in non-production environments only. For more about this, please see the Exception Reporting Guide.

Environment variables

Since Hoodoo is based on Rack, you use variable RACK_ENV as you would use, say, RAILS_ENV for testing and can use things like rackup (with an appropriate config.ru file present, see later) to start it. For example, you might start your service in production mode as follows:

RACK_ENV=production bundle exec rackup

Various other environment variables consulted by Hoodoo are described in the Environment Variables Guide.

API design

It is very strongly recommended that resource API designers opt for backwards compatibility at all times. Implement future extensions to an existing API either via optional fields or entirely new resources, such that existing client code can continue to work without modification. Client authors usually have better things to do with their time than repeatedly rewrite their existing code base to adapt to the whims of incompatible API changes!

Resources have an associated integer major version number as part of the primary routing mechanism. When necessary, major API changes for a resource should be introduced using a new major resource version.

A Minimal Service

Although the service shell is the recommended way to get started with a new service (see later), there’s nothing much magic about the way that Hoodoo uses Ruby subclasses and files. As shown on the Home page, you can bring one up a simple resource with just a single Ruby file. Here, we look at that file more closely, section by section.

A much more fully-featured example using service shell, persistence and presentation features is given in the Active Record Guide but this minimal example is a good place to start to undestand the basics.

Required gems

Since this is a minimal example, we aren’t using Bundler or a Gemfile but you can do so if you wish; otherwise, manually install Thin, Rack and Hoodoo directly:

gem install thin
gem install rack
gem install hoodoo

The service code

The rest of this example all goes into a single Ruby file. Call it anything you like, e.g. service.rb.

Obviously, the Hoodoo service file needs to start by including Hoodoo itself. Since we are going to use Rack to start up the Rack application directly within this single file, we also need Rack – and that’s all. Always include Rack first [1], [2].

require 'rack'
require 'hoodoo'

The implementation class

Hoodoo service classes refer to interface class(es) for the resource(s) hosted by that service. In turn, the interface classes refer to the implementation class(es). That means – no matter what order we actually write the code – that we need to define the implementation class first in the parse order. Class names are entirely up to you, though the pattern of FooImplementation and FooInterface is recommended.

class TimeImplementation < Hoodoo::Services::Implementation
  def show( context )
    context.response.set_resource( { 'time' => Time.now.utc.iso8601 } )
  end
end

The five action method names are #show, #list, create, #update and #delete. These are reserved and called by Hoodoo in response to incoming API requests. They all have the same signature shown above, with the context variable, which provides information on the inbound request and is used to define your response. The context object is also used to request an endpoint for a resource-to-resource call – an inter-resource call – via its #resource method. For more, see the RDoc page for the Context class.

Important: Hoodoo specifies that a representation of a resource is returned in API responses for all actions.

This means that if successful, the implementations of #show, create, #update and #delete must call context.response.set_resource with a single resource representation before exiting, as shown above. The implementation of #list must call context.response.set_resources (plural) with an Array of resource representations before exiting. See the RDoc page for the Response class for details (noting that #set_resource is an alias of #body).

In this example we are just returning an arbitrary Hash, but resource representations really should be properly rendered through the Presenter layer to ensure consistency and accuracy. The DSL for this layer can also be used to specify (and Hoodoo will then automatically validate) inbound creation or update payloads. For information on Presenters, see the Presenters Guide.

If the implementation fails, it should add one or more error messages to its response object via the #add_errors method. If the implementation throws an uncaught exception, Hoodoo itself will catch it and return a well-formed HTTP 500 / platform.fault error.

Important: While many parts of Hoodoo accept parameters as Symbols or Strings, for inbound payloads and rendered data, Hoodoo does not use an “indifferent access” Hash. Keys in outbound data must be Strings and keys in all inbound data will be Strings.

Security additions

Additional optional method #verify is used as part of the security layer; see the RDoc page for the Implementation class and the Security Guide for full information.

Before and After filters

Hoodoo supports exactly two filters, both with the same signature as the action methods; a single context parameter is given. The methods are simply called #before and #after. The first can be used to provide additional checks or input changes before any action method is called; the latter can be used to check or modify output, or take additional actions such as bespoke extended output data logging.

Generally speaking filter methods can be easily misused and lead to a degree of “invisible magic” in a request-response processing chain, which is why Hoodoo only provides very simple, limited facilities. They should be used as sparingly as possible.

Arising reserved method names

You can define whatever private or protected methods you want, but you must avoid the reserved method names already listed - actions #show, #list, create, #update and #delete; filters #before and #after; and security checker #verify.

The interface class

The interface class can describe your resource in some detail, but the minimal case is simple:

class TimeInterface < Hoodoo::Services::Interface
  interface :Time do
    endpoint :time, TimeImplementation
    public_actions :show
  end
end

The interface method takes the name, as a Symbol or String, of the resource – here, :Time – and a block which uses the interface DSL to describe the resource. In this minimal case, we declare that the resource is located at an endpoint including the URI fragment time (again given as Symbol or String) and give the implementation class directly. The interface version is not specified to defaults to 1 – the resource will therefore be found at a URI path of .../v1/time.

All service actions by default are protected by a security layer and require a session to access them. See Security Guide for details. If your service implements all actions, it need declare nothing. If it defines a subset of session-protected actions, it can use the actions method to list them. If it defines any public actions – these can be called with no session – then it uses the public_actions method, as shown here.

The full range of DSL “commands” available within the interface block are described by the RDoc page for the Interface class. The suggested reading order is:

  1. #interface (as a refresher)
  2. #endpoint and #version
  3. #actions and #public_actions
  4. #embeds if you know about embedding – see the Hoodoo API Specification for details
  5. #to_list to specify more about how you handle lists (index views), such as additional sort keys, or search and filter information – see the Hoodoo API Specification for details
  6. #to_create, to_update and update_same_as_create if you want validation of inbound create/update payloads; see also the Presenters Guide
  7. #secure_log_for if you have sensitive data inbound or outbound that shouldn’t be logged; see also the Security Guide and the Logging Guide
  8. #errors_for if you want to add custom error messages to responses; the Hoodoo API Specification lists the API defaults and the RDoc page for the Errors class to understand what’s going on behind the scenes (see in particular its #add_error method )
  9. #additional_permissions_for if you want to make inter-resource calls and require additional permissions in addition to those offered by the inbound caller’s session, you request them here. See the Security Guide for details.

The more complete and precise your interface declaration, the greater the safety net that Hoodoo provides. Your service can benefit from up-front validation at the resource description level of inbound data, not get called for actions it doesn’t implement, participate in the security model and so-on all from a few simple DSL calls. Your API will end up more robust and more consistent. It is best practice to describe interfaces thoroughly.

Public actions and sessions

Although your service can declare actions that are accessible from anyone at any time, you’ll probably want to prevent arbitrary, unauthorised calls. This is handled by sessions. Any protected action needs a valid session in order to be processed.

In a RACK_ENV mode of development or test, Hoodoo uses an internal test session which permits any access to any resource. In other environments, Hoodoo looks sessions up in Memcached. You must set environment variable MEMCACHED_HOST for your service to run successfully (e.g. MEMCACHED_HOST=127.0.0.1:11211 RACK_ENV=production bundle exec rackup).

The session system is described in full in the Security Guide.

The service class

The service class is simple; it just declares all resource interfaces that exist within this service application.

class TimeService < Hoodoo::Services::Service
  comprised_of TimeInterface
end

You can use a comma-separated list for the comprised_of call and/or multiple comprised_of calls (according to your preferred coding style) to make the declarations. For more, see the RDoc page for the Service class

If intending to wrap up several resources within a single service, try to name your service class according to the overall intent. For example, a service class comprised of Credit and Debit resources might be called FinancialService.

Active Record

In the simple example here, we aren’t using any persistence layer such as a database. In particular, we are not using Active Record. If you don’t have that installed as a gem under your current Ruby environment, there is no problem. If the Active Record constant is defined however, Hoodoo detects this and activates its Active Record support code, described in the Active Record Guide. An attempt to start up the example service would fail as Hoodoo would attempt to connect to a database without any connection configuration. So, just in case, and purely for the purposes of making this example work even if you have Active Record present, we add a hack:

# This is a hack for the example and needed if you have Active Record present,
# else Hoodoo will expect a database connection.
#
Object.send( :remove_const, :ActiveRecord ) rescue nil

…and simply un-define the entire Active Record namespace from Object, so that Hoodoo doesn’t detect it.

Bring-up with Rack

Finally, we need to tell Rack about our Hoodoo-based Rack application and start it. Here, we tell Rack to use the Thin web server on port 9292:

builder = Rack::Builder.new do
  use( Hoodoo::Services::Middleware )
  run( TimeService.new )
end

Rack::Handler::Thin.run( builder, :Port => 9292 )

This is just one of many ways to bring up a Rack application; there are many tutorials and bits of documentation about Rack available online if you search around, plus of course the core Rack documentation itself.

Running the service

Now you have the service declarations and Rack startup all inside a file called (say) service.rb and gems installed, so you can start the service easily – add bundle exec in front of the command if you used Bundler for the gems:

ruby service.rb

The service is now ready for use:

curl http://127.0.0.1:9292/v1/time/now \
     --header 'Content-Type: application/json; charset=utf-8'
{"time":"2015-08-03T02:31:34Z"}

Don’t forget to include the Content-Type header exactly as shown above for all API calls.

Searching, filtering and embedding

Lists of resources can be searched (include-on-match) or filtered (exclude-on-match). It’s up to the class describing the interface of a resource to declare the things it allows for these operations, if anything. Likewise, a resource might also allow someone to request that its representation includes other embedded data – usually, some important related resource – to make life easier for callers; one call instead of two, that kind of thing.

The caller-side implementation of this is all described by the Hoodoo API Specification. As mentioned earlier, the code implementation is managed via the #to_list DSL – see to_list and the ToListDSL class in RDoc for details.

It’s important to be aware of the options for searching and embedding in particular, as efficient API design can depend upon it – especially once inter-resource calls get involved.

Inter-resource calls

Overview

Often, one resource will want to call another resource as part of its general operation. If the two resources are running inside the same service application, you could just directly access the data model underneath the target resource. Unfortunately this can introduce security or scoping errors and breaks encapsulation, coupling the two resources together forever inside the same service application.

Instead, it’s better to use an inter-resource call, a high level construct which amounts to – via several layers of abstraction – a local method to a resource in the same service application, or a real remote call to another service application if required. The semantics for both are the same; resources always “look” as if they’re remote.

Code

Suppose a Clock resource were implemented in terms of a Time and a Date resource. To show the Clock, it has to show the Time and the Date internally.

def show( context )
  time_resource = context.resource( :Time, 1 )
  date_resource = context.resource( :Date, 1 )

  time = time_resource.show( '<id>' ); return if time.adds_errors_to?( context.response.errors )
  date = date_resource.show( '<id>' ); return if date.adds_errors_to?( context.response.errors )

  context.response.set_resource(
    {
      'it_is' => time[ 'time' ] + " on " + date[ 'date' ]
    }
  )
end

The interface is exactly like Hoodoo::Client, which has its own Guide. You first ask for an endpoint for a given resource and API version (the default is 1). Then you make calls through this endpoint following the familiar action names – show, list, create, update or delete. The Hoodoo::Client Guide gives more information about the parameters and options for those methods.

Every time you make an inter-resource call you MUST always check for errors in the result. There are two ways to do this. One is as shown above; it is an ugly design pattern because it mutates its input parameter, but it leads to terse code:

result = some_resource.action( parameters )
return if result.adds_errors_to?( context.response.errors )

This takes any errors in result, adds them to your context.response.errors collection and returns if any were added; else it continues and you can examine result as if it were an Array or Hash containing the call’s expected on-success data. A cleaner pattern avoids input parameter mutation at the expense of slightly more verbose code:

result = some_resource.action( parameters )

context.response.add_errors( result.platform_errors )
return if context.response.halt_processing?

Here, we get the response object to add to its own errors collection any other errors from the collection in result.platform_errors (if the result’s platform errors collection is empty, nothing happens). Then we exit if the response now indicates an error condition; that’s also the idiomatic pattern for data level validation and other error conditions (e.g. see the Active Record Guide).

For more on these two approaches, see the RDoc documentation for platform_errors.

Performance

Minimise inter-resource calls

When an inter-resource call is talking to a “locally” implemented resource – one in the same service application – there’s some overhead involved in constructing the request so that the target resource “thinks” it’s just receiving any normal call and in processing the result, but this isn’t too severe. Ultimately it’s still just a series of local method calls. The overhead is worthwhile given the resulting simplicity of service implementation, resource decoupling and inherent reliance (without code duplication) on all of the existing security in the target resource’s implementation.

When an inter-resource call is talking to a remotely implemented resource, the overhead can be significant; HTTP calls, AMQP messages or other wire protocols will be involved and the ‘other end’ may respond slowly.

With this in mind, minimising the inter-resource call count is a good idea. Construct your APIs such that a target resource does as much as possible in one call to ease the burden on the upstream resource. This improves internal performance and potentially provides extra facilites that all API users of the target resource might find valuable.

The embedding problem

When someone asks for a list of some resource and wants the result to include some other sub-resource embedded in the response, it’s often due to a desire to offer a resource-level representation of relational data. For example, a Member might belong to an Account. Someone might list Accounts, asking to embed the list of Members – if your API supported that.

One obvious implementation for Account would be to get a page of list results of Accounts and iterate over that list. For each one, you’d ask for a list of Members where the member account ID matched the list entry’s ID (e.g. via a supported list item search key of account_id). This would work, but be slow and scale poorly. A list page of 500 Accounts could result in 500 inter-resource calls for Members!

Work around this with API design on the target, embedding resource side – in this example, Member; here, we could simply support a comma-separated list of account IDs via an account_ids (plural) parameter. The Account resource gets its list, assembles the IDs of the list entries into a query string, then makes just the one inter-resource call to get associated Members. This is still not without overhead of course; now it must hold not only the list of Accounts but also the big list of Members in memory, then embed the Member data into the Account list by walking the Account list and matching the IDs in the Members list. It may need to call the Member service again for further pages of Member data too, but you can tune the page size to balance the memory requirement against inter-resource call overhead.

Running many services locally

If you have split your resources across multiple service applications, you may want to run those all locally for easy development. There are many ways to do this, but probably the easiest is just to bring up services using guard which starts them on randomised spare ports. If you are developing a particular resource endpoint, bring the containing service up last, on a well known port, so API calls from the likes of curl or Postman don’t need to be set up with configurable port numbers.

For example, suppose you are writing service_new and this makes calls to resources hosted in service_a and service_b.

# In one terminal...
cd service_a
bundle exec guard
# In another terminal...
cd ../service_b
bundle exec guard
# Then in a third...
cd ../service_new
PORT=9292 bundle exec guard

This works because Hoodoo includes a Distributed Ruby (DRb)-based discovery engine that’s activated by default for local development. Whenever a service launches for the first time, the Hoodoo DRb registry is run in the background and then keeps running, taking note of the resource endpoints declared by each service when run and the port on which it has been launched. This means inter-resource calls should “just work” both locally and remotely, provided that the service in question is up.

If you have any trouble, shut down your services then kill the DRB daemon. You might find more than one is running, or that it has become confused about port numbers after a few service restarts. On Mac OS X, the following command will list them all:

ps -Af | grep ruby | grep drb_server
514102323 36434   1   0 Mon04pm ??   0:02.72 /Users/user/.rbenv/[...]/by_drb/drb_server_start.rb
514102323 66598   1   0 26Nov15 ??   0:02.06 /Users/user/.rbenv/[...]/by_drb/drb_server_start.rb

…so here it’s obvious that two discoverers have ended up running. Shut down any running services and kill the discoverer daemons with kill <pid> (e.g. for the above cases - kill 36434; kill 66598). When you restart the services afterwards, you should find that only one server is running.

The Service Shell

Hoodoo has no opinions on how you arrange your code, it just needs the service’s various subclasses defined appropriately. Although you can do the hard work of structuring a bespoke service layout, it’s often easier to start from the “service shell” – a template for new services.

Creating a new Service

Just as you can create a skeleton Rails application using the rails command, so you can create a skeleton Hoodoo service using the hoodoo command. With the Hoodoo gem installed:

hoodoo service_foo

…will create a new service called “Foo” in a folder called service_foo by cloning an empty shell from GitHub, removing the .git folder and renaming generic name placeholders where appropriate.

Remember, you can have one service for each resource you write, or put several resources into a single service. It helps to have some rough idea about how you want to proceed at the start, so that your service can have a more appropriate name that’s less likely to be a source of confusion in future, as you develop more resource endpoints.

If you want to customise the service shell and use that customised copy for future services, fork the shell and point Hoodoo at the fork in the hoodoo command. See hoodoo --help for details.

File and folder layout

This section is correct at the time of writing and will always be broadly correct, though there may be small changes/additions that happen over time in the shell code in Git which don’t necessarily get immediately reflected in this Guide. The root folder of the shell contains a README.md which can be consulted first, before being updated with your service-specific “read me” information.

Basic setup

├── CHANGELOG.md - You should keep this up to date!
├── Gemfile      - For Bundler
├── Gemfile.lock - For Bundler
├── Guardfile    - For using 'guard'; see README.md and read Guardfile
├── Rakefile     - For 'rake' - see "bundle exec rake --tasks"
├── README.md    - You should update/modify this as needed

Much of the root folder contains fundamental prerequisites. As you’ll see from reading the Gemfile, Active Record and ActiveSupport are included as the database ORM and for general utility use. They function well outside of Rails and provide a familiar pattern for the persistence layer which helps service authors who would otherwise be in initially an entirely unfamiliar API world of Hoodoo. You can remove these if you want though.

When you’re happy with the Gemfile, don’t forget to:

bundle install

…so that you can use bundle exec rake... and similar thereafter.

Important: The service shell is designed with a minimal useful gem footprint. Reduction of external dependencies is useful for security and manageability of your service. You should run bundle update often!

Optional setup

Some things in the root folder are helper files for particular tools, which are only of interest if you use those tools – e.g. local Ruby version via RBEnv, Travis for CI, Docker for deployment and so-on. You can ignore many of the configuration files when they’re tool-specific. The Docker workflow and Git tagging in particular may not suit your approach or organisation even if you do use Docker and Git.

├── .dockerignore             - Useful if you use Docker
├── .gitignore                - Useful if you use Git
├── .ruby-version             - Useful if you use RBEnv
├── .travis.yml               - Useful if you use Travis
│
├── VERSION                   - Optional Git workflow - update via 'bin/version_bump'
├── bin
│   ├── generators
│   │   ├── classes           - Folder with supporting code for generators (see below)
│   │   ├── effective_date.rb - Effective dating generator (see below)
│   │   └── templates         - Folder with supporting templates for generators
│   └── version_bump          - Update VERSION, does tagging etc. in Git; see script
│                               for details.

The bin folder contains optional helpers.

Database and environments

The shell is more “opinionated” than Hoodoo by necessity. It needs to know where it’s going to include files, the requirement order and so-on, and where to look to get everything set up for running under Rack locally, or in deployed environments over conventional HTTP or HTTP-over-AMQP via Alchemy. A Rails-like configuration approach is taken with a config folder containing database information, environment-specific files – remember, that’s RACK_ENV, NOT RAILS_ENV! – and the initializers folder for custom startup code. It contains out-of-box ways to easily optionally enable Raygun, Airbrake and/or NewRelic.

The Shell introduces the idea of three environments:

You can add new environments just by using the right RACK_ENV value and, optionally, adding an appropriately named file into the environments folder.

├── config
│   ├── database.yml       - Same as the equivalent file in Rails
│   ├── environments
│   │   ├── development.rb - See the contents of these files for details.
│   │   ├── production.rb  - Usually, there are very few environment-specific
│   │   └── test.rb        - pieces of information needed by a service.
│   ├── initializers       -
│   │   ├── airbrake.rb    - Uncomment and add your Airbrake API key if need be
│   │   ├── new_relic.rb   - Uncomment and ensure "newrelic.yml" is filled in
│   │   └── raygun.rb      - Uncomment and add your Raygun API key if need be
│   └── newrelic.yml       -
├── db                     -
│   ├── migrate            - See "bundle exec rake --tasks" (use 'g:migration')
│   │   └── .gitkeep       -
│   ├── schema.rb          - Same as the equivalent file in Rails
│   └── seeds.rb           - Same as the equivalent file in Rails
├── log                    -
│   └── .gitkeep           - By default, log files are written here, as in Rails

If you’re using Active Record to store data, you should read about the recommended approach for resource implementation code via the Active Record Guide.

The startup process

Now we get into the more interesting bits! The shell is quite small so it is easy to read all of the code to understand exactly what everything is doing and completely demystify your entire code base. We really, really strongly recommend you do that.

├── config.ru      - The starting point. This is what `rackup` reads.
├── environment.rb - The first thing that `config.ru` loads.

You can add other folders to the loaders at the end of environment.rb by copying the pattern of lines already there. You might, for example, add in automatic loading of any files in a lib folder you create.

See the README.md file for a list of commands you can use to start up the service.

The actual service code

Filenames given here are useful conventions but not mandatory. You can call files anything you want and split the classes up any way and anywhere you want within the folders that environment.rb automatically includes (see above), but it’s generally a good idea to stick to the conventions for clarity. Only the service.rb file must be kept as described above, defining a class ServiceApplication that’s a descendant of Hoodoo::Services::Service, because config.ru relies upon it.

For each resource endpoint you want to create you should do the following. Suppose the resource was called Account`:

├── service
│   ├── implementations    - Conventional location of implementation classes
│   │   └── .gitkeep
│   ├── interfaces         - Conventional location of interface classes
│   │   └── .gitkeep
│   ├── models             - Conventional location of Active Record models
│   │   └── .gitkeep
│   ├── monkeys            - Conventional location of Hoodoo::Monkey code
│   │   └── .gitkeep
│   └── resources          - Conventional location of resource classes
│       └── .gitkeep
│
├── service.rb             - Update `comprised_of` with new Interfaces

Testing

Testing is done via RSpec with a coverage report produced by RCov RCov which can be read by opening coverage/rcov/index.html in your preferred web browser.

Important: When writing tests, note that global RSpec monkey patching is disabled so you’ll need to call RSpec.describe, rather than just describe, to describe tests.

Given the above, a minimal test file looks something like this:

require 'spec_helper.rb'

RSpec.describe 'foo' do
  # "it", "before", "context", etc. blocks
end

The folder structure for the service’s test suite is:

└── spec
    ├── factories                  - Use of this is optional, via FactoryBot;
    │   └── .gitkeep                 contents are included by 'spec_helper.rb'
    ├── generators                 - Shell's own generator tests; self-checks;
    │   └── *.rb                     you can keep or delete at your discretion
    ├── service
    │   ├── implementations        - Rare tests for implementation class code
    │   │   └── .gitkeep
    │   ├── integration            - 'get', 'post', etc. DSL-based API testing
    │   │   ├── .gitkeep
    │   │   └── example_spec.rb    - Read the contents, then delete this file
    │   ├── interfaces             - Extremely rare tests for interface code
    │   │   └── .gitkeep
    │   ├── models                 - Tests of model logic, if any is present
    │   │   └── .gitkeep
    │   ├── monkeys                - Tests of monkey patches, if any exist
    │   │   └── .gitkeep
    │   └── resources              - Tests to verify resource schema
    │       └── .gitkeep
    ├── spec_helper.rb             - Sets everything up for RSpec
    └── support                    - Contents are included by 'spec_helper.rb'
        ├── app_for_integration.rb - Read the comments in these files to
        ├── database_cleaner.rb      understand how each one helps you with
        ├── factory_bot.rb           tests or what changes you might need to
        └── rack_test.rb             make.
Modifying the coverage report

Underneath the test coverage report from RCov is SimpleCov. By default the RCov formatter is used because its output is lighter weight – faster in a web browser and the lack of syntax highlighting makes “red” untested lines in source code stand out far better – but it doesn’t fully support groups. You might want those, or want the syntax highlighting of the SimpleCov formatter. To achieve this, remove the line that says SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter in spec_helper.rb. Note that your coverage reports will now be found one level up, in coverage/index.html, rather than down in coverage/rcov/index.html.

Further reading

For more information on writing service tests, recommended practices and advanced techniques, please see the Testing Guide.

Next Steps