In software development, it is easy for developers to sometimes choose tools that are not perfect for the job. This could be because maybe it made sense at that particular time, we were facing a tight deadline, we were not familiar enough with our tools, or a plethora of particular circumstances. It is always beneficial to consider alternatives.
Recently, we have encountered a few situations where using alternatives to our “default” tools improved our project code. The tools that were already in place did the job sufficiently well, but replacing them with simpler ones made more sense in these situations.
Feature Specs and View Specs
We had a situation where feature specs were overused, and we replaced them with view specs successfully.
In this particular project, we used rspec for each module, and feature specs for integrating the whole lot. In one case, all we were checking was that the message intended for admin users was displayed for admin users, and not regular users.
We were checking the page contents in both cases here, but feature specs actually used real web browsers in a “headless” manner, while view specs simply rendered templates.
The code was like this:
erb
<% if current_user.admin? %>
<%= @admin_message %>
<% end %>
We had feature specs like these:
ruby
feature 'user notifications' do
scenario 'admin message is not shown' do
login_as user
...
expect(page).not_to have_content(latest_admin_message)
end
end
feature 'admin notifications' do
scenario 'admin message is shown' do
login_as admin
...
expect(page).to have_content(latest_admin_message)
end
end
We replaced these with view specs:
ruby
context 'user notifications' do
before do
allow(user).to receive(:admin?) { false }
end
it 'does not show admin message'
render
expect(rendered).not_to have_content(latest_admin_message)
end
end
context 'admin notifications' do
before do
allow(user).to receive(:admin?) { true }
end
it 'shows admin message' do
render
expect(rendered).to have_content(latest_admin_message)
end
end
Unit tests cannot always replace integration tests, but in this case, this was sufficient for us. In fact, this eliminated many other potential causes of failure that we were not interested in, and the view specs ran much faster.
Another important benefit of this was that it has made the specs more focused and clearer about our intentions.
VCR and Webmock
In a separate situation, the VCR gem was used to capture the remote API response where direct use of Webmock would simplify the code.
If you don’t know what VCR does, it lets us “record” web responses to requests, and play it back later so we don’t have to access the real remote server every time. It is extremely valuable in testing since it removes the dependency on external services. While it is no replacement for manual, real-world tests, it lets us simulate very closely.
It saves a lot of information in yaml files. This is not all, but it looks like this:
yaml
---
http_interactions:
- request:
method: get
uri: http://example.com/api/
body:
encoding: US-ASCII
string: ''
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
response:
status:
code: 200
message: OK
and so on.
The problem we had, was that we had tests that used recorded and then modified response files. Let’s say we want to test our code against errors, like 500, or 200 with broken JSON contents. These error scenarios should be tested but they are not easy to actually record, since we usually don’t (or at least we hope not to) receive a lot of malformed responses or internal server errors from remote services. In our test cases, we had modified response files (or “vcr cassettes”) to suit our needs.
Instead of modifying saved response files, we simplified this by using Webmock’s stub_request
:
ruby
stub_request(:any, remote_api_address).to_return { { status: 500, body: '' } }
or:
ruby
stub_request(:any, remote_api_address).to_return { { status: 200, body: 'Malformed JSON!' } }
This made our specs a lot easier to read since we did not need all the extra details VCR would save for us in these cases.
One of the major advantages of VCR is the ability to update the input files by recording the updated responses but, we were not interested in that here. This rewrite made it more focused and easier to understand the intentions.
Other Gems
Similarly, we have come across situations where it made sense to replace a decorator gem with our own, simpler decorators that did less. There were also some other gems we have removed in the past, like the one that helped us with memoization. These gems worked well, and they were very good for what they were intended for. However, it does not mean we should use third-party tools just because their functionalities cover what we need.
Summary
Every tool has its advantages and disadvantages. Every time we add a third-party tool, we are accepting all the disadvantages that come with it. Keeping the code simple and free of unnecessary complexity makes it easier to read and maintain. We should always try to choose the simplest tools that do the job. It’ll help us keep things simpler, because simpler code is always easier to work with.