Posted about 6 years ago. Visible to the public.

Testing shared traits or modules without repeating yourself

When two classes implement the same behavior (methods, callbacks, etc.), you should extract that behavior into a trait or module. This card describes how to test that extracted behavior without repeating yourself.

Note that the examples below use Modularity traits to extract shared behavior. This is simply because we like to do it that way at makandra. The same techniques apply for modules and overriding self.included.


Say you have two classes Page and Template. Both contain the same behavior: They have a string field #html which needs to be sanitized (stripped of malicious HTML) before validation:

# app/models/page.rb class Page before_validation :sanitize_html private def sanitize_html self.html = Sanitize.clean(html) end end # app/models/template.rb class Template before_validation :sanitize_html private def sanitize_html self.html = Sanitize.clean(html) end end

You should extract this behavior into a trait or module, so you can reduce the code to this:

# app/models/shared/sanitize_html_trait.rb module SanitizeHtml as_trait do before_validation :sanitize_html private def sanitize_html self.html = Sanitize.clean(html) end end end # app/models/page.rb class Page does 'sanitize_html' end # app/models/template.rb class Template does 'sanitize_html' end

Testing test trait usage with a shared example group

When two classes share behavior through a common trait or module, the tests for those two classes should share a shared example group:

# spec/models/shared_examples/sanitize_html_trait.rb shared_examples_for SanitizeHtmlTrait do it 'should strip malicious HTML during validation' do subject.html = 'before <script>alert("hacked!")</script> after' subject.valid? subject.html.should == 'before after' end end # spec/models/page_spec.rb describe Page do it_behaves_like SanitizeHtmlTrait end # spec/models/template_spec.rb describe Template do it_behaves_like SanitizeHtmlTrait end

If your trait takes arguments, simply remember that shared_examples_for can have parameters.

Other approaches

There is another approach to test traits by instantiating an anonymous class that uses the trait under test. I dislike this approach because it always ends up in a stubfest that doesn't really test anything.

I recommend testing traits with shared example groups as described above.

By refactoring problematic code and creating automated tests, makandra can vastly improve the maintainability of your Rails application.

Owner of this card:

Henning Koch
Last edit:
about 6 years ago
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more