Developing Gems with TDD and Minitest: Part 3
Part 2 left off with our project having a post class that is able to parse YAML Front Matter from our Markdown files, and then process the Markdown as well. Now we need a way to have templates for our Posts as well as writing the files to our site directory.
The Template
First, we need to create the file lib/mark/template.rb
to hold our class and the matching test file at test/template_test.rb
.
For our template class we want to instantiate it with a path to our application’s template and give it a write method that accepts the content that we want to use inside the template and a path to where we want to write the file.
module Mark
class Template
def initialize(path)
@file_contents = File.read(path)
parse
end
private
def parse
@unrendered = Tilt::ERBTemplate.new { @file_contents }
end
end
end
Our initializer is simple, we just pass it a path and read the file at that path and then call the private method pase. We don’t do any error checking right now, but that is definitely something we should handle later. Our parse method is also very basic, we just create our @unrendered
class variable to be an unrendered Tilt
template.
We also need to write our write
method, which takes two arguments. The first argument is the content that will be rendered inside of our template, and the second argument is the path to write the file at.
module Mark
class Template
def initialize(path)
@file_contents = File.read(path)
parse
end
def write(content, path)
file = File.open(path, 'w')
file.write(@unrendered.render { content })
end
private
def parse
@unrendered = Tilt::ERBTemplate.new { @file_contents }
end
end
end
Our write method is pretty basic, open a writeable file at the path we pass it and then write our rendered content to the file. Where it gets interesting is how we write our tests for this class. This is where we start using mocks and stubs. If you don’t know what mocks and stubs are, mocks are fake objects that test that certain methods have been called on it, and stubs “fake” methods and return what you specify it.
We’re using the built in mock and stub features of Minitest. Minitest mocks and stubs aren’t very powerful like some other alternatives such as Mocha or rspec-mocks.
Before we get started writing our template tests, create the file test/fixtures/valid_template.rb
. This will be our basic template so we can test if our write
method writes the correct content.
<html>
<%= yield %>
</html>
require 'test_helper'
class TemplateTest < MiniTest::Unit::TestCase
def setup
@template_path = File.expand_path('../fixtures/valid_template.erb', __FILE__)
end
def test_content_writes_correct_content
file_path = File.expand_path('../fixtures/', __FILE__) + '/test.html'
file = MiniTest::Mock.new
file.expect :write, nil, ["<html>\n hi\n</html>\n"]
File.stub :open, file do
template = Mark::Template.new(@template_path)
template.write 'hi', file_path
end
file.verify
end
end
We only have one test here because we only have one public method, and it’s a fairly simple test. First we assign file_path
to where we want to write our file. We also create a mock, then expect that mock to have the write method called on it by the time we call verify on it.
Then we get to our stub, we stub out File.open
and make it always return our file
mock while inside the block we pass to it. Inside of that block we create a new instance of our template class and pass the string ‘hi’ to it. Finally we hit file.verify
which ensures that our mock had the write
method called on it, returned nil, and was passed the arguments inside of the array.
We don’t use our Post
class in this test because it and our Template
class are separated quite a bit. If we decided to create and use an instance of Post
we’d have the possibility of failing this test for no reason if the Post
. If you wanted to test the two working together, you should probably write an integration test.
Now we have our Post class and our Template class. Since our template class can write content now, all we need is some way to tie these two together. In the next post we’ll work on making a class that lets us use these classes together to write out posts.