MiniTest, as the name suggests, is a small and fast unit testing framework. Shipped with Ruby 1.9, MiniTest supports a complete suite of testing capabilities such as TDD, BDD, mocking, and benchmarking.
This quick reference aims to demonstrate MiniTest's main concepts and provide real world examples to get you acquainted quickly. Let's start with MiniTest::Spec.
MiniTest::Spec
Provides RSpec-like matchers and contexts right out of the box.
require 'minitest/autorun'
describe Hipster, "Demonstration of MiniTest" do
before do
@hipster = Hipster.new
end
after do
@hipster.destroy!
end
subject do
Array.new.tap do |attributes|
attributes << "silly hats"
attributes << "skinny jeans"
end
end
let(:list) { Array.new }
describe "when asked about the font" do
it "should be helvetica" do
@hipster.preferred_font.must_equal "helvetica"
end
end
describe "when asked about mainstream" do
it "won't be mainstream" do
@hipster.mainstream?.wont_equal true
end
end
end
Matchers (must | wont)
In most cases you can switch between must for positive expectations and wont
for negative expectations.
| Assertion | Examples |
must_be |
list.size.must_be :==, 0 |
must_be_close_to |
subject.size.must_be_close_to 1,1 |
must_be_empty |
list.must_be_empty |
must_be_instance_of |
list.must_be_instance_of Array |
must_be_kind_of |
list.must_be_kind_of Enumerable |
must_be_nil |
list.first.must_be_nil |
must_be_same_as |
subject.must_be_same_as subject |
must_be_silent |
proc { "no stdout or stderr" }.must_be_silent |
must_be_within_epsilon |
subject.size.must_be_within_epsilon 1,1 |
must_equal |
subject.size.must_equal 2 |
must_include |
subject.must_include "skinny jeans" |
must_match |
subject.first.must_match /silly/ |
must_output |
proc { print "#{subject.size}!" }.must_output "2!" |
must_respond_to |
subject.must_respond_to :count |
must_raise |
proc { subject.foo }.must_raise NoMethodError |
must_send |
subject.must_send [subject, :values_at, 0] |
must_throw |
proc { throw :done if subject.any? }.must_throw :done |
MiniTest::Unit::TestCase
Provides a rich set of assertions to make your tests clean and readable.
require 'minitest/autorun'
class TestHipster < MiniTest::Unit::TestCase
def setup
@hipster = Hipster.new
@list = Array.new
@subject = ["silly hats", "skinny jeans"]
end
def teardown
@hipster.destroy!
end
def test_for_helvetica_font
assert_equal "helvetica!", @hipster.preferred_font
end
def test_not_mainstream
refute @hipster.mainstream?
end
end
Assertions (assert | refute)
Toggle between assert for positive assertions and refute for negative assertions.
| Assertion | Example |
assert |
assert @subject.any?, "empty subjects" |
assert_block |
assert_block { @subject.any? } |
assert_empty |
assert_empty @list |
assert_equal |
assert_equal 2, @subject.size |
assert_in_delta |
assert_in_delta @subject.size, 1,1 |
assert_in_epsilon |
assert_in_epsilon @subject.size, 1, 1 |
assert_includes |
assert_includes @subject, "skinny jeans" |
assert_instance_of |
assert_instance_of Array, @list |
assert_kind_of |
assert_kind_of Enumerable, @list |
assert_match |
assert_match @subject.first, /silly/ |
assert_nil |
assert_nil @list.first |
assert_operator |
assert_operator @list.size, :== , 0 |
assert_output |
assert_output("Size: 2") { print "Size: #{@subject.size}"} |
assert_raises |
assert_raises(NoMethodError) { @subject.foo } |
assert_respond_to |
assert_respond_to @subject, :count |
assert_same |
assert_same @subject, @subject, "It's the same object silly" |
assert_send |
assert_send [@subject, :values_at, 0] |
assert_silent |
assert_silent { "no stdout or stderr" } |
assert_throws |
assert_throws(:error,'is empty') {throw :error if @subject.any?} |
MiniTest::Mock
A simple and clean mock system. There two essential methods at our disposal:
expect and verify.
require 'minitest/autorun'
class Twipster
def initialize(twitter)
# A Ruby wrapper for the Twitter API
@twitter = twitter
end
def submit(tweet)
@twitter.update("#{tweet} #lolhipster")
end
end
describe Twipster, "Make every tweet a hipster tweet." do
before do
@twitter = MiniTest::Mock.new
@twipster = Twipster.new(@twitter)
end
it "should append a #lolhipster hashtag and update Twitter with our status" do
tweet = "Skyrim? Too mainstream."
@twitter.expect :update, true, ["#{tweet} #lolhipster"]
@twipster.submit(tweet)
assert @twitter.verify # verifies tweet and hashtag was passed to `@twitter.update`
end
end
Resources
- MiniTest on Github
- MiniTest Rdoc
- Using MiniTest::Spec with Rails
- Ruby Inside: A MiniTest::Spec Tutorial: Elegant Spec-Style Testing That Comes With Ruby
I hope you found this quick guide valuable. Please let me know if you'd like to see anything else included and feel free to ask questions or give feedback in the comments section.
Callbacks are a great technique for achieving simplicity and flexibility. Simply put, a callback is a block of code passed as an argument to a method. In Ruby, code blocks are everywhere and Ruby makes it trivial to pass a block of code to methods. For example:
def foo(bar, &block)
callback = block
callback.call(bar)
end
foo(5) {|x| x * x} # => 25
But what do we do when a method needs two blocks of code or more? Consider the classic case where we want a method to execute a block of code if an action succeeds or call different code if an action fails.
In this article, I will demonstrate how we can pass multiple blocks to a method and with some metaprogramming, we can achieve a dynamic callback mechanism with just a few lines of code.
Let's add a method called callback to the Proc class:
class Proc
def callback(callable, *args)
self === Class.new do
method_name = callable.to_sym
define_method(method_name) { |&block| block.nil? ? true : block.call(*args) }
define_method("#{method_name}?") { true }
def method_missing(method_name, *args, &block) false; end
end.new
end
end
That's it! The above Proc#callback method simply yields an anonymous class
with methods defined to handle our callbacks. This allows for the capability of
creating and storing dynamic callbacks, which can later be looked up and
executed as needed.
Notice anything unusual? We're using the === operand to invoke the
block. Proc#=== is an alias for Proc.call. Anything on the right side of
=== acts as the proc's parameter. Normally, this is to allow a proc object to
be a target of a when clause in case statements, but we're using it as a super
simple way of invoking our anonymous class.
Let’s try it with something useful. Let’s say we’re writing something which needs to happen in an all-or-nothing, atomic fashion. Either the whole thing works, or none of it does. A simple case is tweeting:
def tweet(message, &block)
Twitter.update(message)
block.callback :success
rescue => e
block.callback :failure, e.message
end
The tweet method accepts a message string and &block parameters. We call
callback on the block and give it a name. Any name will work :success, :error,
:fail!, whatever. In addition, we can pass arguments to the blocks (more on that
later). Now we can provide a status if the tweet was successful or not:
tweet "Ruby methods with multiple blocks. #lolruby" do |on|
on.success do
puts "Tweet successful!"
end
on.failure do |status|
puts "Error: #{status}"
end
end
The advantage here is that we define our own mini DSL. We don't need to worry
about passing too many or unexpected blocks. We could have easily said
where.success or on.error or update.fail!. Also note the on.failure
block includes a status parameter - this contains the exception message
captured in the tweet method above. So if Twitter was down for whatever
reason, the on.failure block would be invoked and printed 'Error: Twitter is
down or being upgraded'.
Bonus: In addition to wrapping code in blocks, our Proc#callback method
defines boolean style methods. So we could have call the tweet method like this
if we wanted to:
tweet "Ruby methods with multiple blocks. #lolruby" do |update|
puts "Tweet successful!" if update.success?
puts "Sorry, something went wrong." if update.failure?
end
Put the Proc#callback method in a utility library and your code will look neat and tidy.
As always, I welcome your thoughts and feedback. Let me know what you think of the techniques shown here, or share your own favorite code block tricks.
I watch a lot of tests run in a given day. So I figured why not make it more fun. Inspired by minitest's pride, and um cats? I came up with a Nyan Cat inspired RSpec formatter.
Update: After last week's launch, Nyan Cat received a great response from the Ruby world. Over the weekend, I released version 0.0.2. It includes a few bug fixes and some really cool enhancements. Most notably, Nyan Cat now spans multiple lines. In addition, it displays running totals of passing, pending, and failed specs. Thanks to everyone who contributed! Checkout the new screencast below.
-_-_-_-_-_-_-_,------,
_-_-_-_-_-_-_-| /\_/\
-_-_-_-_-_-_-~|__( ^ .^)
_-_-_-_-_-_-_-"" ""
Nyan Cat
Much like Nyan, Nyan Cat simply creates a rainbow trail of test results. It counts the number of examples as they execute and highlights failed and pending specs. The rainbow changes colors as it runs and if all the specs pass, Nyan Cat falls asleep. If there are any pending or failing specs, Nyan cat is concerned and can't sleep.
Here's a short demo of Nyan Cat in action.
Installing Nyan Cat is easy. Just install the gem nyan-cat-formatter and simply put the options in your .rspec file:
--format NyanCatFormatter
Checkout the code on Github and let me know how you like it. If you run into any issues, please create an issue on Github and I will be sure to get it fixed. Of course you can always fork the project and send me a pull request.
Have fun!
Gaga originated from my winning entry in Codebrawl's Key/Value Store contest. The challenge was to write the best key/value storage backend you can think of. Since Git is fast, reliable, and a great tool for storing source code, I was really interested in making an easy way to store key/values.
Built with Grit, Gaga supports SET, GET, KEYS, and DELETE operations. And since it's Git, we can easily enhance it to include other awesome Git features such as branches, diffs, reverting, etc.
Usage
@gaga = Gaga.new(:repo => '/path/to/repo')
# SET
@gaga['lady'] = "gaga"
# GET
@gaga['lady'] #=> "gaga"
# KEYS
@gaga.keys #=> ['lady']
# DELETE
@gaga.delete('lady') #=> 'gaga'
# Remove all items from the store
@gaga.clear
That works pretty well. Now, we can harness the power of Git and enhance our data store. For example, we can get a history log for a specific key:
# LOG
@gaga.log('lady')
# Produces:
[
{"message"=>"all clear","committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."},
{"message"=>"set 'lady' ", "committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."}
{"message"=>"delete 'lady' ", "committer"=>{"name"=>"Matt Sears", "email"=>"matt@mattsears.com"}, "committed_date"=>"2011-09-05..."}
]
This is just a start. There's still a lot things we can add. If you are interested in more detailed information, check out the repo on Github.
I've just released Stamps - A Ruby gem for creating postage labels, calculate the shipping cost of packages, standardize domestic addresses via USPS CASS certified Address Matching Software, and track shipments using the Stamps.com Web Services API.
Quick Start
First, you will need to register for a (free) developer account at Stamps.com. Once you receive your test credentials and integration id, just plug them into the configuration block:
Stamps.configure do |config|
config.integration_id = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX'
config.username = 'STAMPS USERNAME'
config.password = 'STAMPS PASSWORD'
end
For a simple test, we can call Stamps.account to retreive
information about the account. By default, Stamps will return all
responses as a Hash.
Create a Stamp
First, we need to standardize the shipping address that complies with the USPS address formatting guidelines:
standardized_address = Stamps.clean_address(
:address => {
:full_name => 'The White House',
:address1 => '1600 Pennsylvania Avenue, NW',
:city => 'Washington',
:state => 'DC',
:zip_code => '20500'
})
Now that we have a clean address we can create a new stamp. The
Stamps.create! takes the sender and receiver address along with parameters
on the rate:
stamp = Stamps.create!(
:rate => {
:from_zip_code => '45440',
:to_zip_code => '20500',
:weight_oz => '6.5',
:ship_date => Date.today.strftime('%Y-%m-%d'),
:package_type => 'Package',
:service_type => 'US-FC' # Flat-rate
},
:to => standardized_address,
:from => {
:full_name => 'Littlelines',
:address1 => '50 Chestnut Street',
:address2 => 'Suite 234',
:city => 'Beavervcreek',
:state => 'OH',
:zip_code => '45440'
}
)
Hooray!
That's it! Stamps will return a url of the stamp. Print it and ship it!
stamp[:url]
If you are interested in more detailed information, check out the repo on Github.