Unit Testing Sass Frameworks

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.

Sass Framework Extensions

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?

Enter Ruby Here-Doc Strings

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.

Implementation Time

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.

Final Sass Tests

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!

Other Resources

by Ken Collins
AWS Serverless Hero