Skip to content

Integration testing

Igor Balos edited this page Feb 10, 2020 · 24 revisions

There are a few ways to test your integration with Postmark when you're using the ruby gem. The simplest approach we've found utilizes Webmock and VCR to record real requests and response details which you can then write assertions against.

This approach minimizes the amount of manual stubbing needed against the Postmark gem and lets you focus on the integration instead of stubbing our API.

Writing integration tests with Webmock

Here's a simple ruby script that integrates with Postmark by sending the user an email and then based on the response from Postmark return result whether the user email was sent over to Postmark.

user.rb file content

require 'postmark'

POSTMARK_TOKEN = 'your-server-token'

class User
  attr_accessor :email_address, :emailed

  def send_email
    client = Postmark::ApiClient.new(POSTMARK_TOKEN)

    begin
      email = client.deliver(from: '[email protected]',
                             to: @email_address,
                             subject: 'Hello World',
                             text_body: 'Welcome!')

      @emailed = email[:error_code] == 0

      email
    end
  end
end

In the example above, we are verifying that the email was sent by checking the error_code in the response body from Postmark and setting emailed to true. Eventually we'd like to add some additional functionality such as storing the message_id returned from Postmark so that we can store that alongside the user but this will work for now.

Then we could use that class in the following way to write our basic test case:

user_spec.rb file content

require 'rspec'
require_relative 'user'

RSpec.describe User do
  let(:user) { User.new }

  describe '#send_email' do
    before { user.email_address = '[email protected]' }

    context 'success' do
      subject do
        user.send_email
      end

      it 'marks as emailed' do
        user.send_email
        expect(user.emailed).to be_truthy
      end
    end
  end
end

Now that we have a functioning Postmark integration test case it'd be nice to be able to add a spec that we can run offline and without needing a connection to the Postmark API. In order to do that we'll start off by just utilizing Webmock to mock our responses and then we'll later add in VCR to automatically stub our requests. Go ahead and install webmock via Bundler or gem install webmock.

If we go ahead and run that spec(rspec user_spec.rb.) we will see that Webmock blocks the API request from happening and provides instructions on how to stub the request. Now lets go ahead and add in a stub for the request.

Since we know from the documentation that an ErrorCode of 0 means a successful send we'll go ahead and just return that in the response for our spec.

Note: Webmock provides the ability to mock as much as you need about the request, add and adjust the stub as needed for your use case.

require 'rspec'
require 'webmock/rspec'
require_relative 'user'

RSpec.describe User do
  let(:user) { User.new }

  describe '#send_email' do
    before { user.email_address = '[email protected]' }

    context 'success' do
      subject do
        user.send_email
      end

      it 'marks as emailed' do
        stub_request(:post, "https://api.postmarkapp.com/email")
          .to_return(status: 200, body: { ErrorCode: 0 }.to_json)

        subject

        expect(user.emailed).to be_truthy
      end
    end
  end
end   

Now if we run our spec again we should see that it successfully passed indicating that our send_email function is working as expected. Webmock is great for simpler specs where you only care about a few small details but if you want to deal with more elaborate requests it can be nice to let VCR step in and do the heavy lifting to stub requests with the real API.

Further testing with VCR

Go ahead and install VCR via Bundler or gem install vcr.

To start utilizing VCR we just need to add a simple configure block and then give it a cassette to record the request in. Go ahead and make the directory where VCR will store it's cassettes(mkdir -p fixtures/vcr_cassettes). The cassettes will contain the recorded request and response for every external web request.

require 'rspec'
require 'webmock/rspec'
require 'vcr'
require_relative 'user'

VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes"
  config.hook_into :webmock
end

RSpec.describe User do
  let(:user) { User.new }

  describe '#send_email' do
    before { user.email_address = '[email protected]' }

    context 'success' do
      subject do
        user.send_email
      end

      it 'marks as emailed' do
        subject
        expect(user.emailed).to be_truthy
      end
    end
  end
end

Now that we've configured VCR we can write our first live spec. For this spec we'll verify a few additional details from the request such as the message_id and error_code.

require 'rspec'
require 'webmock/rspec'
require 'vcr'
require_relative 'user'

VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes"
  config.hook_into :webmock
end

RSpec.describe User do
  let(:user) { User.new }

  describe '#send_email' do
    before { user.email_address = '[email protected]' }

    context 'success' do
      subject do
        VCR.use_cassette("successful_notification") do
          user.send_email
        end
      end

      it 'has a message_id' do
        expect(subject[:message_id]).to_not be_empty
      end

      it 'does not have an error' do
        expect(subject[:error_code]).to eq(0)
      end

      it 'marks as emailed' do
        subject
        expect(user.emailed).to be_truthy
      end
    end
  end
end

Now we have a few expectations in place based on the actual API response details:

  • We're ensuring that we're getting a message_id back, we'd eventually want to store this alongside the user so we could for instance link to the Postmark dashboard for that message.
  • We're checking that the error_code is 0 which ensures that there wasn't an error with the request.
  • Lastly, we're checking that because the email was sent properly we updated the emailed attribute to be true for that user indicating that they were sent the email.

Now that we have that in place we can go ahead and run our spec which will hit the live Postmark API the first time we run it and record the cassette. For subsequent runs it'll utilize the cassette to replay the request instead of hitting the Postmark API which will also speed up our spec and allow us to run it offline or in our CI.

  rspec user_spec.rb
...

Finished in 2.67 seconds (files took 0.2091 seconds to load)
3 examples, 0 failures

Running our spec the second time you'll notice our spec finished in a fraction of the time because we've gotten rid of the whole HTTP request cycle, 0.017s vs. 2.67s.

 rspec user_spec.rb
...

Finished in 0.0166 seconds (files took 0.21077 seconds to load)
3 examples, 0 failures

If you inspect the newly created cassette file you'll see the full details from the Postmark API request and response. Now whenever you run this spec VCR will use the cassette details instead of making a live request to the Postmark API.

  cat fixtures/vcr_cassettes/successful_notification.yml
---
http_interactions:
- request:
    method: post
    uri: https://api.postmarkapp.com/email
    body:
      encoding: UTF-8
      string: '{"From":"[email protected]","To":"[email protected]","Subject":"Hello
        World","TextBody":"Welcome!","ReplyTo":"","Cc":"","Bcc":""}'
    headers:
      User-Agent:
      - Postmark Ruby Gem v1.19.1
      Content-Type:
      - application/json
      Accept:
      - application/json
      X-Postmark-Server-Token:
      - your-server-token
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 200
      message: OK
    headers:
      Cache-Control:
      - private
      Content-Type:
      - application/json; charset=utf-8
      X-Postmark-Account:
      - '123456'
      X-Postmark-Server:
      - '123456'
      Server:
      - unicorns-double-rainbow
      X-Powered-By:
      - ASP.NET
      Date:
      - Wed, 15 Jan 2020 18:26:03 GMT
      Transfer-Encoding:
      - chunked
    body:
      encoding: ASCII-8BIT
      string: '{"To":"[email protected]","SubmittedAt":"2020-01-15T13:26:04.5279314-05:00","MessageID":"80e7173d-2e1e-4518-8fbd-9252334fcd41","ErrorCode":0,"Message":"OK"}'
    http_version:
  recorded_at: Wed, 15 Jan 2020 18:26:04 GMT
recorded_with: VCR 5.0.0