---
title: Test Incoming Webhook Requests with Faraday
teaser: 'Incorporate incoming webhook requests in your feature testing.

  '
tags: web,testing,ruby
author: Nathan L. Walls
published_on: 2015-05-15
---

I was recently working with a client who has an application that accepts
incoming webhooks from GitHub. We track receipt of these webhooks and display
state information to the user.

We test this functionality with request specs, which provide unit-level
guarantees. We also want to use a Capybara feature spec to cover the entire
workflow, not just individual units in the system.

Previously, we've written about [stubbing external services in
tests][stub-external]. We pursue that strategy elsewhere for testing this
application, but it isn't a fit for this problem. In this situation, we need the
webhooks triggered outside of the application to be received by the application
after a user begins one task and before they proceed to the next step in the
application workflow.

First, let's add some code in to support responding to webhooks, using Faraday:

```ruby
# spec/support/fake_github_webhook.rb
require "faraday"

class FakeGitHubWebhook
  def initialize(fixture:, host:, path:, port:)
    @fixture = fixture
    @host = host
    @path = path
    @port = port

    load_fixture
    construct_connection
  end

  def send
    connection.post do |request|
      request.url path
      request.headers = headers
      request.body = JSON.generate(body)
    end
  end

  private

  attr_accessor(
    :body,
    :connection,
    :fixture,
    :headers,
    :path,
    :session
  )

  def construct_connection
    @connection = Faraday.new(url: "http://#{@host}:#{@port}")
  end

  def fixture_path
    "#{Rails.root}/spec/fixtures/github_webhooks/#{fixture}"
  end

  def load_fixture
    fixture_json = JSON.parse(File.read(fixture_path))

    @headers = fixture_json.fetch("headers")
    @body = fixture_json.fetch("body")
  end
end
```

This application needs to receive several different webhook calls. In support of
that, this class will load a JSON-formatted fixture file to populate request
headers and the request body.

To create the fixture, we can look at the webhooks configured against the
application and see example successful requests in GitHub. If your GitHub
repository has webhooks configured for it, you can see those by going to
`Settings` then `Webhooks & Services` in your repository. You can then see
`Recent Deliveries`, which will look similar to this:

![Recent webhook deliveries](https://images.thoughtbot.com/faraday-testing-webhooks/webhooks-list.png)

Click on one of the hash values and you'll see headers and the request payload,
like so:

![Expanded webhook delivery](https://images.thoughtbot.com/faraday-testing-webhooks/expanded-webhooks.png)

Copy that data and make a JSON fixture file like the following, in
`spec/fixtures/github_webhooks/ping.json`:

```json
{
  "headers": {
    "Content-Type": "application/json",
    "User-Agent": "GitHub-Hookshot/9a55c2d",
    "X-GitHub-Delivery": "ae35f100-e900-11e4-8a9e-df298010fa27",
    "X-GitHub-task": "ping"
  },
  "body": {
    "zen": "Keep it logically awesome.",
    "hook_id": 4648018,
    "hook": {
      "url": "https://api.github.com/repos/example-user/test-repository/hooks/4648018",
      "test_url": "https://api.github.com/repos/example-user/test-repository/hooks/4648018/test",
      "ping_url": "https://api.github.com/repos/example-user/test-repository/hooks/4648018/pings",
      "id": 5555,
      "name": "web",
      "active": true,
      "tasks": [
        "*"
      ]
    }
  }
}

```

The repository name and GitHub user name are important in the sense that they
need to line up with our test expectations. We're using `example-user` and
`test-repository`, respectively. You may see other information in the headers
and payload you want to sanitize.

If you're hand-rolling these fixture files, validate them with a
[JSON linter][lint]. I decided to make the process easy by installing `jsonlint`
locally, which let me test the fixture files like so:

```sh
% npm install jsonlint -g
% jsonlint spec/fixtures/github_webhooks/ping.json
```

Now, we're ready to write a Capybara feature spec using `FakeGitHubWebhook`:

```ruby
require "rails_helper"

feature "User opens task", :js do
  scenario "and can complete GitHub steps" do
    user = create(:user, username: "example-user")
    task = create(:task)
    ping_verification = task.steps.first.verification

    sign_in(user: user)
    visit task_path(task)
    click_on t("links.begin_task")

    expect(page).to have_content(ping_verification.error_text)

    fake_github_webhook("ping").send

    visit current_url
    expect(page).to have_content(ping_verification.success_text)
  end

  def fake_github_webhook(fixture)
    FakeGitHubWebhook.new(
      fixture: "#{fixture}.json",
      host: Capybara.current_session.server.host,
      path: "/github/callbacks",
      port: Capybara.current_session.server.port,
    )
  end
end

```

This test is declared as a JavaScript test so Capybara will provide access
to a session backed by a running instance of the application at `127.0.0.1`
instead of an unreachable one at `www.example.com`. Similarly, we need to
provide the port because Capybara is going to run the test instance of the
application on a new port and our `FakeGitHubWebhook` will need to reach it.

We setup our test and start walking through the application workflow. Then, we
can use our `FakeGitHubWebhook` Faraday client to send in a webhook notification
to the application at the spot in the workflow where we expect to have it. Now,
we have an proper end-to-end test, including having the application receive
requests from outside.

[stub-external]: https://thoughtbot.com/blog/how-to-stub-external-services-in-tests
[lint]: http://jsonlint.com
