{"id":4568,"date":"2015-05-11T09:00:05","date_gmt":"2015-05-11T12:00:05","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=4568"},"modified":"2015-06-01T12:22:12","modified_gmt":"2015-06-01T15:22:12","slug":"nobody-told-me-minitest-was-this-fun","status":"publish","type":"post","link":"http:\/\/blog.plataformatec.com.br\/2015\/05\/nobody-told-me-minitest-was-this-fun\/","title":{"rendered":"Nobody told me Minitest was this fun"},"content":{"rendered":"

Ever since I started working with Ruby I have been using RSpec to test my apps and gems without giving minitest much thought. Recently I started a new non-Rails project and decided to give Minitest a try just for the fun of it. Migrating from one tool to another was refreshingly fun due to the fact that that Minitest and RSpec aren’t so different from each other – they both have the basic features that we need in a testing library to get things running, and if you are used to testing your code moving from one to the other might not be so scary as you might expect.<\/p>\n

Translating testing idioms<\/h2>\n

One of the first things that I looked into was how some of common RSpec idioms should be implemented when using Minitest.<\/p>\n

The classic ones are fairly simple: the before<\/code> and after<\/code> lifecycle hooks should be equivalent as implementing the setup<\/code> and teardown<\/code> methods in your test class, and you have control over the inheritance chain by selecting when\/where to call super<\/code>. let<\/code> and subject<\/code> can be achieved with methods that use memoization to cache their values.<\/p>\n

# A classic RSpec subject\/before usage.\nrequire 'spec_helper'\n\ndescribe Post do\n  subject(:post) { Post.new }\n  before { post.publish! }\nend\n\n# The equivalent with Minitest & Ruby.\nrequire 'test_helper'\n\nclass PostTest < Minitest::Test\n  def post\n    @post ||= Post.new\n  end\n\n  def setup\n    post.publish!\n  end\nend\n<\/code><\/pre>\n

RSpec shared examples, where you can reuse a set of examples across your test suite, can be replicated by simply writing your tests in modules and depend on accessor methods to inject any objects your tests might depend on<\/p>\n

# What used to be a shared_examples 'Serialization' can be a module...\nmodule SerializationTests\n  def serializer\n    raise NotImplementedError\n  end\nend\n\n# And your test cases can include that module to copy the tests\nclass JSONSerializationTest < Minitest::Test\n  include SerializationTests\n\n  def serializer\n    JSON\n  end\nend\n\nclass MarshalSerializationTest < Minitest::Test\n  include SerializationTests\n\n  def serializer\n    Marshal\n  end\nend\n<\/code><\/pre>\n

Mocks and stubs, which are incredibly flexible when using RSpec, are available in Minitest without any third party gem:<\/p>\n

class PostTest < Minitest::Test\n  def test_notifies_on_publish\n    notifier = Minitest::Mock.new\n    notifier.expect :notify!, true\n\n    post.publish!(notifier: notifier)\n    notifier.verify\n  end\n\n  def test_does_not_notifies_on_republish\n    notifier = Minitest::Mock.new\n\n    post.stub :published?, true do\n      post.publish!(notifier: notifier)\n      notifier.verify\n    end\n  end\nend\n<\/code><\/pre>\n

If you want a different or more fluent API, you can use something like mocha<\/code><\/a> to improve your mocks, or even bring RSpec API into the mix – with some manual setup you can pick the rspec-mocks<\/code><\/a> gem and define your mocks and stubs just like when using the complete RSpec tooling:<\/p>\n

require 'rspec\/mocks'\n\nclass PostTest < Minitest::Test\n  include ::RSpec::Mocks::ExampleMethods\n\n  def before_setup\n    ::RSpec::Mocks.setup\n    super\n  end\n\n  def after_teardown\n    super\n    ::RSpec::Mocks.verify\n  ensure\n    ::RSpec::Mocks.teardown\n  end\n\n  def test_notifies_on_publish\n    notifier = double('A notifier')\n    expect(notifier).to receive(:notify!)\n\n    post.publish!(notifier: notifier)\n  end\nend\n<\/code><\/pre>\n

Know your assertions<\/h2>\n

One of my favorite parts of RSpec is how expressive the assertions can be – from the Ruby code that we have to write to the errors that the test runner will emit when something is broken. One might think that we can have something similar when working with Minitest, but that is not exactly true.<\/p>\n

Let’s say we want to test a method like Post#active?<\/code>. Using a dynamic matcher from RSpec like expect(post).to be_active<\/code> will produce a very straightforward message when that assertion fails: expected #<Post: \u2026>.active? to return false, got true<\/code>.<\/p>\n

With Minitest, we might be tempted to write an assertion like assert !post.active?<\/code>, but then the failure message wouldn’t be much useful for us: Failed assertion, no message given<\/code>. But fear not, because for something like this we have the assert_predicate<\/code> and refute_predicate<\/code> assertions, and they can produce very straightforward failure messages like Expected #<Post:\u2026> to not be active?.<\/code>, which clearly explains what went wrong with our tests.<\/p>\n

Besides the predicate assertions, we have a few other assertion methods that can useful instead of playing with the plain assert<\/code> method: assert_includes<\/code>, assert_same<\/code>, assert_operator<\/code> and so on – and every one of those has a refute_<\/code> counterpart for negative assertions.<\/p>\n

It’s always a matter of checking the documentation – The Minitest::Assertions<\/code> module<\/a> explains all the default assertions that you use with Minitest.<\/p>\n

And in the case where you want to write a new assertion, you can always mimic how the built-in assertions are written to write your own:<\/p>\n

module ActiveModelAssertions\n  def assert_valid(model, msg = nil)\n    msg = message(msg) { \"Expected #{model} to be valid, but got errors: #{errors}.\" }\n    valid = model.valid?\n    errors = model.errors.full_messages.join(', ')\n    assert valid, msg\n  end\nend\n\nclass PostTest < Minitest::Test\n  include ActiveModelAssertions\n\n  def test_post_validations\n    post = Post.new(title: 'The Post')\n    assert_valid post\n  end\nend\n<\/code><\/pre>\n

Active Support goodies<\/h2>\n

If you want some extra sugar in your tests, you can bring some of extensions that Active Support has for Minitest that are available when working with Rails – a more declarative API, some extra assertions, time traveling and anything else that Rails might bring to the table.<\/p>\n

require 'active_support'\nrequire 'active_support\/test_case'\nrequire 'minitest\/autorun'\n\nActiveSupport.test_order = :random\n\nclass PostTest < ActiveSupport::TestCase\n  # setup' and teardown' can be blocks,\n  # like RSpec before' and after'.\n  setup do\n    @post = Post.new\n  end\n\n  # 'test' is a declarative way to define\n  # test methods.\n  test 'deactivating a post' do\n    @post.deactivate!\n    refute_predicate @post, :active?\n  end\nend\n<\/code><\/pre>\n

Tweaking the toolchain<\/h2>\n

Minitest simplicity might not be so great when it comes to the default spec runner and reporter, which lack some of my favorite parts of RSpec – the verbose and colored output, the handful of command line flags or the report on failures that get the command to run a single failure test. But on the good side, even though Minitest does not ship with some of those features by default, there are a great number of gems that can help our test suite to be more verbose and friendly whenever we need to fix a failing test.<\/p>\n

For instance, with the minitest-reporters<\/a> gem you can bring some color to your tests output or make it compatible with RubyMine and TeamCity. You can use reporters that are compatible with JUnit or RubyMine if that’s your thing. You can use minitest-fail-fast<\/a> to bring the --fail-fast<\/code> flag from RSpec and exit your test suite as soon as a test fails. Or you can track down object allocations in your tests using minitest-gcstats<\/a>.<\/p>\n

If any of those gems aren’t exactly the setup you want it, you can always mix it up a bit and roll your own gem<\/a> with reporters, helpers and improvements that are suitable for the way you write your tests.<\/p>\n

Thanks to this extensibility, Rails 5 will bring some improvements to how you run the tests in your app to improve the overall testing experience with Rails (be sure to check this Pull Request<\/a> and the improvements from other Pull Requests).<\/p>\n

\n\"Subscribe<\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"

Ever since I started working with Ruby I have been using RSpec to test my apps and gems without giving minitest much thought. Recently I started a new non-Rails project and decided to give Minitest a try just for the fun of it. Migrating from one tool to another was refreshingly fun due to the … \u00bb<\/a><\/p>\n","protected":false},"author":17,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[237,60,96],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4568"}],"collection":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/17"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=4568"}],"version-history":[{"count":13,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4568\/revisions"}],"predecessor-version":[{"id":4578,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/4568\/revisions\/4578"}],"wp:attachment":[{"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=4568"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=4568"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=4568"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}