Sass Tutorial: Unit Testing with Sass True

Feb 21, 2020 - 10 min read
Dale Sande
editor-page-cover

Some say that if your code doesn’t have tests, you don’t have shippable code. When it comes to CSS or Sass unit testing, there are few tools to choose from.

Today, we’ll explore the testing tool Sass and show you some of it’s top features.

This mini-tutorial will focus on setting up Sass True, the Sass unit testing framework developed by Miriam Suzanne.

Here’s what we’ll cover today:


Master advanced Sass techniques

Create your own Sass web projects using hands-on instruction.

Sass for CSS: Advanced Frontend Development


What is Sass Unit Testing?

Unit testing in Sass is not much different from unit testing in any other language. There are methods to define a test module, a method to wrap a series of tests or assertions, then there are 4 assertion-types: assert-true, assert-false, assert-equal and assert-unequal.

The only other thing to consider is that there are two different patterns to follow for testing. One for functions that will evaluate the output of the function, the other is for mixins which expects a specific return based on the configuration of the mixin.


Install Sass True

For the sake of clarity, this tutorial is going to assume that there are no files other than the Sass we want to test and the test files.

With a clean directory, you want to run the following installation command:

$ npm i node-sass sass-true glob jest

Now create two directories:

$ mkdir src tests

In your package.json, update to the following:

"scripts": {
  "test": "jest"
},

With everything installed, let’s write some code.


Join Sass True with Jest

This is the last part of the setup needed to get the jest command working with Sass True. We need to write a small JS shim.

In the tests dir, create the new shim file:

$ touch tests/scss.spec.js

In this new file, add the following code:

const path = require('path')
const sassTrue = require('sass-true')
const glob = require('glob')
 
describe('Sass', () => {
  // Find all of the Sass files that end in `*.spec.scss` in any directory of this project.
  // I use path.resolve because True requires absolute paths to compile test files.
  const sassTestFiles = glob.sync(path.resolve(process.cwd(), 'tests/**/*.spec.scss'))
 
  // Run True on every file found with the describe and it methods provided
  sassTestFiles.forEach(file =>
    sassTrue.runSass({ file }, { describe, it })
  )
})

This shim is taking the instructions from the Sass True docs and adding some superpowers to make things much easier. Mainly, this is automatically looping through all the spec files so that you don’t have to write out all the absolute paths to the files as Sass True requires.

With this in place, we are ready to start writing tests.


Implement Sass True Function

Sass True only will test mixins and functions. After all, that is all you need to ensure that your tooling is always working correctly. Testing the CSS output itself is another tool’s job.

In your project, create the new Sass file that we will use as the function to test.

$ touch src/_map-deep-get.scss

In the file, add the following code:

/// This function is to be used to return nested key values within a nested map
/// @group utility
/// @parameter {Variable} $map [null] - pass in map to be evaluated
/// @parameter {Variable} $keys [null] - pass in keys to be evaluated
/// @link https://css-tricks.com/snippets/sass/deep-getset-maps/ Article from CSS-Tricks
/// @example scss - pass map and strings/variables into function
///   $map: (
///     'size': (
///       'sml': 10px
///     )
///   );
///   $var: map-deep-get($tokens, 'size', 'sml'); => 10px
///
 
@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }
  @return $map;
}

This is a pretty common function that is added to Sass projects.


Testing a Sass Function

Let’s create the new file needed to test the Sass. Remember that in the shim, we are looking for any file that matches *.spec.scss, so to test our map function, we can create a file name like this.

$ touch tests/mapDeep.spec.scss

What’s powerful with Sass True is that it’s written with Sass. Because of this, all the features of Sass are available to you when writing tests.

In this new file, let’s import the dependencies we need. First, we need to import True, then we need to import the Sass function which we want to test.

@import 'true';
@import '../src/map-deep-get';

Looking at the code for the map function, it’s clear that we need a map to use in this test.

$map: (
  'size': (
    'sml': 10px
  )
);

For the test, the first thing needed is to describe the test you are running. This will help you see the results of the test in the CLI output.

To me, the docs were a little hard to follow as they still describe the actual mixins used in Sass True. It appears that for compatibility with testing frameworks like Jest, several aliases were created. I will try my best to address these cross references in the code.

Remember in the shim we have the argument of { describe, it }. In Sass True, for example, @mixin test-module() is aliased as describe() and the test() mixin is aliased to the it() mixin.

@include describe('map-deep-get()') {
  ...
}

Next, you need to define the test. This will use the it() mixin.

@include describe('map-deep-get()') {
  @include it('should return the value from a deep-map request') {
    ...
  }
}

Last we need the test itself. In this example, we will use the asset-equal method.

assert-equal: Assert that two parameters are equal. Assertions are used inside the test() mixin to define the expected results of the test.

The assert-equal() mixin takes four arguments, but we are only going to use $assert and $expected.

assert-equal($assert, $expected);

Put the function inside the parentheses of the assert-equal() mixin as if we were to use it in Sass. As illustrated, we can also take advantage of the $map variable we set earlier.

ProTip: in your project, if you have a series of variables you are already using and need these available for the test, simply @import them before running the test(s).

For the $expected argument of the test, we put in the expected value.

@include describe('map-deep-get()') {
  @include it('should return the value from a deep-map request') {
    @include assert-equal(
      map-deep-get($map, 'size', 'sml'), 10px
    );
  }
}

At this point, you should have a setup that has all the libraries needed, a starter function to test, and a working test.


Running the Test

At this point, it’s all downhill. From the command line, run your test.

$ npm test

All things working correctly, you should see the following:

PASS  tests/scss.spec.js
  Sass
    map-deep-get()
      ✓ should return the value from a deep-map request (1ms)
 
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.422s
Ran all test suites.

The Sass Mixin

At this point, you should have Sass True set up and have the map function test working. Moving forward you may want to test a mixin. Testing a mixin is only slightly different than testing a function. For the mixin test, let’s test a common pattern of creating a breakpoint mixin.

$ touch src/_breakpoint--lg.scss

The mixin itself will look like the following:

/// Standard breakpoint to support resolutions greater than 1232px.
/// @group responsive
/// @example scss - Set breakpoint
///   .breakpoint--lg {
///       @include breakpoint--lg {
///         color: orange;
///       }
///   }
@mixin breakpoint--lg {
  @media screen and (min-width: $breakpoint-lg) {
    @content;
  }
}

Testing the Sass mixin

In the mixin, you may have noticed that there is the global var $breakpoint-lg as part of the mixin. To make this a little more ‘real-world’, let’s create a global vars file and define the value of this variable.

$ touch src/_globals.scss

And in this file, will put the variable and its value.

$breakpoint-lg: 1232px;

Getting to the test, let’s create the test file.

$ touch tests/breakpoint.spec.scss

To start this file, we will define all our dependencies.

@import 'true';
@import '../src/globals';
@import '../src/breakpoint--lg';

Next, just like the function test, we need to describe this mixin test.

@include describe('breakpoint--lg()') {
  ...
}

Next, we’ll define what it is expected to do.

@include describe('breakpoint--lg()') {
  @include it('should return content within pre-defined media query') {
    ...
  }
}

With the mixin, we can simply use the assert() method for our test.

@include describe('breakpoint--lg()') {
  @include it('should return content within pre-defined media query') {
    @include assert {
        ...
    }
  }
}

Within the assert() method, we want to test the content to be evaluated using the output() method.

output: Describe the test content to be evaluated against the paired expect() block. Assertions are used inside the test() [or it()] mixin to define the expected results of the test.

Ok, so that simply means that within the output() mixin, you can include whatever Sass you want that uses the mixin you want to test. For example, let’s create a selector that uses the breakpoint mixin and inject the @content value inside the mixin.

@include describe('breakpoint--lg()') {
  @include it('should return content within pre-defined media query') {
    @include assert {
      @include output {
        .breakpoint--lg {
          @include breakpoint--lg {
            color: orange;
          }
        }
      }
    }
  }
}

For the last part, we need an assertion of what the output CSS will be. For this, we will use the expect() method.

expect: Describe the expected results of the paired output() block. The expect() mixin requires a content block and should be nested inside the assert() mixin, along with a single output() block. Assertions are used inside the test() mixin to define the expected results of the test.

So within the expect() mixin, we just add the expected CSS output.

@include describe('breakpoint--lg()') {
  @include it('should return content within pre-defined media query') {
    @include assert {
      @include output {
        .breakpoint--lg {
          @include breakpoint--lg {
            color: orange;
          }
        }
      }
 
      @include expect {
        @media screen and (min-width: 1232px) {
          .breakpoint--lg {
            color: orange;
          }
        }
      }
    }
  }
}

Running the Test

At this point, you have the Sass test suite with two individual tests running one assertion each.

Running the $ npm test command, you should see the following:

PASS  tests/scss.spec.js
  Sass
    breakpoint--lg()
      ✓ should return content within pre-defined media query (2ms)
    map-deep-get()
      ✓ should return the value from a deep-map request
 
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.512s
Ran all test suites.

Wrapping up

In today’s world, Sass code is getting more and more complex. Testing will therefore become even more important as time goes on.

To help you prepare for creating advanced Sass projects, Educative has created the course Sass for CSS: Advanced Frontend Development. This course has hands on tutorials and examples for all the top Sass techniques like nesting, variables, mixins, partials, and dynamic functions. By the end, you’ll have all the experience you need to create and ship your own Sass projects.

Happy testing!


Continue reading about testing


WRITTEN BYDale Sande

A free, bi-monthly email with a roundup of Educative's top articles and coding tips.