We blog using Jekyll at MapBox, which means that all of our blog posts are written in code. Sometimes we make mistakes though, and missing or invalid metadata can cause layout quirks or unexpected errors. To catch these problems earlier, we decided to treat our blog like we do our code — automated unit tests now run after every commit.
Jekyll is a static site generator. We keep all content in simple text files and Jekyll reads each file and transforms it into HTML. We use Jekyll for all static content on our site — the blog, developer docs, help pages and much more.
Each bit of content, like a blog post or a help document, is a file composed of two parts: metadata stored in YAML, and content written in Markdown.
Here’s what the YAML part of this blog post looks like:
If we forget the author tag, the blog layout breaks. If we write invalid YAML, the blog won’t rebuild and the post will stay in limbo.
Content testing prevents these failures ahead of time. Every blog post is submitted as a pull request on GitHub, and with pull request testing hooked up to Travis CI, every change is run through a test suite that gives the green light.
If there’s a problem, we know immediately.
Travis-CI supports plenty of languages for test suites, and we ended up writing ours in Node.js. Since Jekyll is a Ruby project, Travis installs Jekyll for the compilation and Node for the test runner. We use mocha and assert for our content tests.
Here’s our .travis.yml file:
One important consideration is that all tests must be created (but not necessarily run) synchronously in Mocha, which necessitates using the synchronous variants of some Node functions to build tests dynamically. While writing some of the more complex tests, we found that it was more efficient to load all posts using fs.readFileSync before any tests were run, rather than loading each post asynchronously during its corresponding test. This approach allows for testing one-to-many relationships between posts (such as unique permalinks) while minimizing the time spent loading files from disk.
We first construct a posts object and create a test for each post.
The metadata parsing is wrapped in a try/catch statement because js-yaml throws an error when parsing invalid YAML.
We also check the integration between different posts and confirm that the author of each blog post matches the title of a post in _posts/team/.
We’ve saved ourselves a lot of frustration by automating this little part of our publishing workflow. The integration between Travis CI and GitHub lets everyone on our team, not just developers, benefit from tests and push new posts with confidence.