Building the living style guide for CustomInk's new mobile/responsive sites required that we stand on the shoulders of other great frameworks. All of them well authored and hence well tested. But as we built more tools on top of these giants, we felt the need to test our own framework's code and thus began the search for Sass/CSS testing methods. After surveying the current state of testing, I found myself let down.
To be fair, there are some really clever solutions for this problem and some may work for you. One example is the True unit test framework for Sass. This project is written by Eric Suzanne for his awesome Susy grid framework. True allows you to write assertions in Sass itself but is limited to only value-based assertions. Since our living style guide is built within a Ruby gem, we felt that a solution closer to Ruby would fit our needs. That said, the solutions I propose below can be used by any Sass framework if you choose to test that framework with Ruby.
First, we need some Sass to test. Our awesome framework wants more Sass list power! So we created these two fancy Sass functions below for accessing the first or last item of a list.
@function my-first($list) {
@return nth($list, 1);
}
@function my-last($list) {
@return nth($list, length($list));
}
From here, let's pretend that these extensions are loaded whenever you @import "my_framework";
. With great power comes great responsibility, hence we need to test them. But how?
I have always wanted to use one of the many Ruby here-doc string methods and testing Sass felt like a great opportunity to pay closer attention to these. Here is what I mocked up for our ideal Minitest assertion syntax. It uses the <<-FLAG
vs the <<FLAG
here-doc termination sequence so that indentation is maintained.
it 'my-red' do
assert_sass <<-SASS, <<-CSS
.test { color: $my-red; }
SASS
.test { color: #ee3524; }
CSS
end
This looks really clean to me. It is easy to identify that we have some Sass at the top that we want to render to CSS below. If you are lucky, your editor will even syntax highlight each here-doc string too. Astute testing fans might complain that our fictional assert_sass
arguments' are backwards and that the expected CSS value needs to be the first parameter. I get it, but I think it looks and reads cleaner the other way around. And yes, I know it is Sass not SASS but I wanted to stick with here-doc conventions and uppercase my termination sequences.
Now that the vision is out of the way, we need to make assert_sass
work. Our project added the following Ruby methods to a SassTestHelpers
module that was in turn mixed into our base spec case. The details of where you put them in your own project is up to you.
That out of the way, the first thing we will need is a helper to render Sass.
def render_sass(template, options={})
options[:syntax] = :scss unless options.key?(:syntax)
options[:style] = :compact unless options.key?(:style)
options[:cache] = false
Sass::Engine.new(template, options).render
end
The first argument here is a template string to render and some options to pass down to the Sass rendering engine. Set the default syntax to your own needs. But I suggest keeping the :style
option set to :compact
. We felt this was the best output method for tests. Lastly, this method does not leverage the Sass cache for safety. Just say no to Heisenbugs!
Now we need a method to render a string of Sass within our framework's context. Luckily our gem already adds itself to the Sass load path via the SASS_PATH
environment variable. So all we have to do is import our awesome framework before whatever Sass we want to test. Again, Ruby's here-doc strings come in handy.
def render_my_framework(template="")
render_sass <<-TEMPLATE
@import "my_framework";
#{template}
TEMPLATE
end
So with only two helper methods, we can finally implement assert_sass
. Since Minitest::Spec builds on top of unit style assertions, we delegate our assertion to assert_equal
.
def assert_sass(sass_template, expected_css)
expected = expected_css.squish
actual = render_my_framework(sass_template).squish
assert_equal expected, actual
end
This time we make sure to switch the expected and actual to line up with the proper expectation order. If we did not do this, the test error output would be backwards. Lastly, we are using ActiveSupport squish string extension which removes consecutive whitespace not only from the ends of a string, but from within the middle too. This makes your CSS assertions tolerant to irrelevant white space introduced by indented here-doc strings.
The final tests for our Sass framework's list extensions are below.
it 'my-first' do
assert_sass <<-SASS, <<-CSS
$list: (one two three);
.test { content: my-first($list); }
SASS
.test { content: one; }
CSS
end
it 'my-last' do
assert_sass <<-SASS, <<-CSS
$list: (one two three);
.test { content: my-last($list); }
SASS
.test { content: three; }
CSS
end
The result is a simple declarative test style that boosts our confidence level as we move our living style guide's Sass framework forward. We can literally test anything from values to full expressions. Ruby is a great tool for testing and makes authoring Sass frameworks fun. I hope that you can use something like this within your own project and thanks for reading!