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
Say you have two classes
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.
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.