Developing Gems with TDD and Minitest: Part 2
In part 1 we set up our gem and a few dependencies and got our testing environment ready as well. Now it’s time to actually start writing our gem. We’ll start off with our Post class which when given a path will read the file, parse the YAML Front Matter and render our Markdown content.
First we need to create the file lib/mark/post.rb
to contain our post class.
module Mark
class Post
end
end
We also need to create a matching test file, test/post_test.rb
.
require 'test_helper'
class PostTest < MiniTest::Unit::TestCase
end
Require our new Post
class and dependencies in lib/mark.rb
.
require 'yaml'
require 'tilt'
require 'redcarpet'
require 'fileutils'
require 'date'
require 'mark/version'
require 'mark/post'
Lastly we need to create a fixture so our Post
class can work with real content for us to test on. Create the test/fixtures
directory and a file inside of that directory called valid_post.md
.
---
title: Valid Test Post
date: 02/27/1992
---
# Valid Test Content
This is what our post class expects to be in the file that we will read. It starts with YAML Front Matter which is just YAML wrapped inside of ---
at the top of the file. The rest of the file is a standard Markdown document that we will parse with Redcarpet using Tilt.
Before we write any implementation code, lets go over the specifics of what we want our Post
class to do, write the basic structure that our implementation will follow, then write our tests.
How our Post
class should behave:
- Parse meta from YAML Front Matter as a hash
- Parse date from meta into a
Date
object. - Render Markdown into content
Now that we know what we want our Post
class to do we can write the structure of our app. Based on what we have so far a good starting point would be to read the file into a class variable, then call a method to parse the content.
module Mark
class Post
attr_reader :meta, :content
def initialize(path)
@file_contents = File.read(path)
parse
end
private
def parse
end
end
end
Again, all we do is read the file contents into a class variable and then call our (currently empty) parse method. Since we have a decent starting structure we can write our tests.
require 'test_helper'
class PostTest < MiniTest::Unit::TestCase
def setup
@post = Mark::Post.new(File.expand_path('../fixtures/valid_post.md', __FILE__))
end
def test_meta_returns_hash
assert_instance_of Hash, @post.meta
end
def test_meta_parses_title
assert_equal 'Valid Test Post', @post.meta['title']
end
def test_meta_parses_date
assert_equal Date.new(1992, 2, 27), @post.meta['date']
end
def test_content_valid
assert_equal "<h1>Valid Test Content</h1>\n", @post.content
end
end
If you aren’t familiar with testing or Minitest
, we can define a method called setup
which Minitest will run before each of our tests. This allows us to create a fresh instance of Post
passing it the path to the fixture we created earlier. Then we just test to make sure our parse method returns the correct meta values and parsed Markdown.
Now when you type rake test
you should get E’s and F’s which is exactly what we want. Since we have our tests in place we can start writing our implementation.
def parse
split_content = @file_contents.split(/^---/, 3)
split_content.shift
@meta = YAML::load split_content.shift
@meta['date'] = Date.strptime(@meta['date'], '%m/%d/%Y')
@content = Tilt::RedcarpetTemplate.new {split_content.shift}.render
end
Our implementation is very simple and does just enough to make the tests pass and nothing more. We start off by splitting our content into 3 separate sections, an empty section, the YAML content that was between the ---
pair, and our Markdown content. Since the first element of our split content is empty we just call shift
to remove it.
We then call YAML::load
on the second element of our split comment by using shift
to remove and return it. This loads our front matter into a hash of values but they’re all strings. Since loading our YAML returns strings we parse the date string into a Date
object and replace the old string representation.
Finally we create a new instance of Tilt::RedcarpetTemplate
and pass it a block with the final part of our split content and tell it to render. We assign this to our content instance variable which should complete our implementation.
Now when we run our tests we should have four .
’s which means our tests are passing and our code should be doing what we specified earlier.