Unit Testing for Sass with Sassaby

At Wealthfront we use Sass to write all of our CSS stylesheets. Sass is a powerful CSS pre-processor that allows you to leverage features common in programming languages, but are absent from native CSS. Using Sass variables, conditionals, loops, and functions, you can write extensible CSS that is easier to maintain across a large front-end codebase.

As you can tell by countless posts on this blog, we take testing very seriously at Wealthfront. As a precursor to leveraging all of the Sass features, especially its repeatable functions, we needed a way to ensure that our Sass was adequately tested. This led us to develop and open-source Sassaby, a unit testing library for Sass.

In this blog post I’m going to detail a bit of the thought process that led us here and then use some examples from Wealthfront’s codebase to show some of features of Sassaby.

What Brought us Here

Our needs to test Sass are similar to the needs of any testing library:

  1. Tests should be easy to write in a syntax that is familiar to its target audience.
  2. Assertions should be available for all features that should be tested.
  3. Tests should easily be integrated into our existing build system.
  4. Tests should run quickly in that build system.

While there are a few libraries already available for testing Sass, they are also written in Sass, which broke our first need. Sass is a great language for what it intends to be — a CSS pre-processor. Its syntax is not optimized for writing other types of scripts, such as testing libraries. Also, since it has to be compiled to CSS, writing testing code in Sass also had the downside of having to compile down to a file with the test results (which then had to be parsed). We preferred a testing library that would log the failing tests and immediately exit the build process.

We quickly settled on JavaScript as the language of choice for Sassaby. Writing this library with server-side JavaScript allowed us to:

  1. Integrate with our existing Node testing framework, Mocha.
  2. Use any of the available Node assertion libraries.
  3. Easily integrate with our build system (because we already have Node tests running).
  4. Use node-sass for compilation. This package is the Node wrapper on the libsass library (significantly faster than normal Ruby Sass compilation).
  5. Use the excellent Rework CSS package for parsing compiled CSS into JSON.

Sassaby

We built Sassaby to be testing framework agnostic. It will work with whichever Node testing library you are currently using. For the purposes of the examples in this blog post, however, we will be using Mocha. Setting up Sassaby is easy, as you just tell it which file you want to test:

Sassaby breaks down its features into the main repeatable parts of Sass: mixins, functions, and imports.

Mixins

Mixins allow you to return a repeated CSS rule declaration or set of CSS styles into your stylesheet. Sassaby has two categories of mixins: included mixins (returns a set of CSS styles) and standalone mixins (returns a full CSS rule declaration). Here is one of the standalone mixins from Wealthfront’s grid:

This mixin has two main features that we would want to test. It interpolates the given argument into a class selector and gives it a declaration for center aligning items in the grid. We can test this with Sassaby by setting up the standalone mixin, calling it with an argument, and using some of the built-in assertions.

Sassaby’s calledWithArgs function takes the specified file, the mixin, and its arguments and compiles it to the resulting CSS and an AST. Doing the CSS compilation at this step allows for each mixin to be tested in isolation and with different arguments as needed.

Standalone mixins are different than included mixins, which return only CSS style declarations so that they can be called inside an already defined rule set. Here is one of our included mixins that adds browser prefixes to the CSS filter declaration:

The testing interface for included mixins is very similar. For example we would test this mixin like this:

Functions

Functions are similar to mixins in Sass, but do not deal with actual CSS style selectors or rules. They are commonly used for unit conversions, such as this one that converts pixels to rems:

Sassaby supports a function testing interface similar to its mixin interface, but with different assertions. For this function, we want to test that the division is done correctly and that the correct unit is appended to the end. We can accomplish both of these goals with the equals assertion, but the purposes of this example I have also showed the doesnotEqual assertion.

Imports

The final testable feature in Sassaby is imports, which allow you break out variables, mixins, and styles into a more organized file structure. Let’s say our main file looks like this:

We obviously would want to test that these files are imported. Sassaby allows this through this interface:

Bringing it all Together

One thing that we’ve noticed by starting to test our Sass mixins is that it has forced us to write better single responsibility mixins. Here is one of our old mixins from our grid that handles the creation of rules for reordering columns:

Trying to test this would be a bit too complicated. We would have to test that the loop is creating a new class for each column, that the label is interpolated correctly, and that the rule declaration is one less than the column number. It would be much easier to break it out into two single responsibility mixins:

Doing this will allow us to test both of these mixins in isolation. Also, notice how these mixins rely on an externally defined variable, $grid-columns. Sassaby provides a way to stub in these external dependencies, as shown with the below tests for the broken out mixins:

We hope that this has been a good guide to writing more coherent CSS with Sass and testing it with Sassaby. The assertions used in these examples are only a sample of what is available, and we encourage you to check out the full documentation and download the library on npm. We also take pull requests on GitHub.