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:
Create your own Sass web projects using hands-on instruction.
Sass for CSS: Advanced Frontend Development
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.
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.
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.
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.
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 thetest()
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.
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.
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;
}
}
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 pairedexpect()
block. Assertions are used inside thetest()
[orit()
] 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 pairedoutput()
block. Theexpect()
mixin requires a content block and should be nested inside theassert()
mixin, along with a singleoutput()
block. Assertions are used inside thetest()
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;
}
}
}
}
}
}
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.
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!
Free Resources