pupeno/assert_difference

View on GitHub
lib/assert_difference.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "active_support/core_ext/array/wrap"
require "assert_difference/expectation"

# This is the AssertDifference module, which you should include on your test class to be able to use the
# {#assert_difference} method. To use it with Test::Unit add this code:
#
#   class Test::Unit::TestCase
#     include AssertDifference
#   end
#
# or in Rails:
#
#   class ActiveSupport::TestCase
#     # ...
#     include AssertDifference
#   end
#
# and to use it with RSpec:
#
#   RSpec.configure do |config|
#     config.include AssertDifference
#   end
#
# @author José Pablo Fernández
module AssertDifference
  # Test numeric difference between the return value of an expression as a result of what is evaluated
  # in the yielded block.
  #
  #     assert_difference "Article.count" do
  #       post :create, :article => {...}
  #     end
  #
  # An arbitrary expression is passed in and evaluated.
  #
  #     assert_difference "assigns(:article).comments(:reload).size" do
  #       post :create, :comment => {...}
  #     end
  #
  # An arbitrary positive or negative difference can be specified. The default is 1.
  #
  #     assert_difference "Article.count", -1 do
  #       post :delete, :id => ...
  #     end
  #
  # An array of expressions can also be passed in and evaluated.
  #
  #     assert_difference ["Article.count", "Post.count"], 2 do
  #       post :create, :article => {...}
  #     end
  #
  # A error message can be specified.
  #
  #     assert_difference "Article.count", -1, "An Article should be destroyed" do
  #       post :delete, :id => ...
  #     end
  #
  # Various assertions can be combined into one, instead of writing:
  #
  #     assert_difference "Company.count" do
  #       assert_difference "User.count", 5 do
  #         assert_difference "Article.count", -1 do
  #           post :something
  #         end
  #       end
  #     end
  #
  # you can *now* write:
  #
  #     assert_difference "Company.count" => 1, "User.count" => 5, "Article.count" => -1 do
  #       post :something
  #     end
  #
  # the results of the block is the result of the assert, so you can write:
  #
  #     email = assert_difference "ActionMailer::Base.deliveries.count" => 1 do
  #         Mailer.reset_password_email(@user).deliver
  #     end
  #     assert_equal [@user.email], email.to
  #
  # the expectations can also be ranges, for example:
  #
  #     assert_difference "Article.count" => 1, "sample_comments.count" => 2..4 do
  #       post :something
  #     end
  #
  # @param [String, Array<String>, Hash<String, [Integer, Range]>] expectations Single expectation as a string, an array of expectations or hash table of
  #   expectations and expected difference.
  # @param [Integer, Range, nil] expected_difference Expected difference when using an array or single expression.
  # @param [String, nil] error_message Error message to display on top of the description of the expectation failed.
  # @return [Object] Whatever the block returned.
  def assert_difference(expectations, expected_difference = nil, error_message = nil, &block)
    binding      = block.send(:binding)
    expectations = Expectation.build_expectations(expectations, expected_difference, binding)

    result = yield

    expectations.map &:eval_after

    failed_expectations = expectations.reject &:passed?
    if failed_expectations.any?
      all_error_messages = failed_expectations.map(&:error_message).join("\n\n").strip
      all_error_messages = "#{error_message}.\n#{all_error_messages}" unless error_message.nil?
      fail all_error_messages
    end

    result
  end
end