Testing Outgoing Query Messages and Associations in Ruby on Rails
🎧 Spotlights
More Ruby on Rails exploration
Testing associations is an anti-pattern
Snippets of Code is an engineering journal documenting learnings for experiments and projects.
When one object sends a message to another object, the receiving object should be responsible for testing its API.
Testing outgoing query messages from a consumer perspective is an unnecessary duplication of the test.
To avoid redundancy, we should not test the outgoing query messages. We should not make assertions about their result or expect to send them. If we send a message without side effects, it is invisible to the rest of the application. Therefore, it's an anti-pattern to keep duplicating the test on the outgoing query side.
It's worth noting that the returns from query messages are not plain data but rather calculations. However, it's still considered a query method.
The setup to test an association is designed to trigger a side effect in a different context, allowing us to make assertions around that side effect. Which suggests that associations are an outgoing query message. The method calls an external dependency in the form of a database.
Ruby on Rails has an extensive test suite that includes tests for associations. While tests provide documentation for behavior, a DSL is a concrete example of a higher-level specification. Which is what we define in any ActiveRecord model.
We use Query methods to test associations in the context of the tests. Incoming query messages should be tested by asserting the return value.
It's better to enforce these association relationships at the database level and ensure they are tested. Associations do not represent meaningful concepts from the domain but rather infrastructure details.
If you are enjoying the learning journal, please ❤️
Business rules are procedures or regulations aimed at increasing profitability or reducing costs for a company. They are designed to establish the structure of the business and control or influence its behavior. Associations are none of these rules; they are your configuration code.
If someone will benefit from this content, please share.
💻 👓
This is an example of how the anti-pattern might show up in a code-base.
class Order < ApplicationRecord
belongs_to :user
def total_price
calculate_total
end
private
# In the case of the associatoion the method will be calling the database to retrieve the records
def calculate_total
# Complex calculation to determine total price
end
end
RSpec.describe Order do
describe "#total_price" do
it "calculates the total price of the order" do
user = User.create(name: "Alice")
order = Order.create(user_id: user.id)
# Stubbing the calculation method for the purpose of this test
allow(order).to receive(:calculate_total).and_return(100)
expect(order.total_price).to eq(100)
end
end
end
🧪 Next Actions
More experimentation with Ruby on Rails code.
Reading about Elixir and Data-Oriented programming