-
Notifications
You must be signed in to change notification settings - Fork 71
Integration testing
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.
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.
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
is0
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:
- abcxyz-123456
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
For additional information about the capabilities of the Postmark API, see Postmark Developers Documentation.
- Email sending
- Test email sending
- Bounces
- Templates
- Templates push
- Server
- Servers
- Message Streams
- Webhooks
- Messages
- Domains
- Sender Signatures
- Stats
- Trigger Tags
- Suppressions
- Data Removals
- Trigger Inbound Rules
- Parsing Inbound
- Using Postmark with Mail library
- Accessing Postmark Message ID
- Error Handling
- Integration Testing
- Troubleshooting
- Known issues and how to resolve them