Friday, 4 February 2011

assert_difference for any value

I like assert_difference. It does exactly what it says on the tin - and in a nice, rubyish way.

However, I noticed that you can't assert a difference in something that isn't numeric. You can assert that creating a Customer will change the Customer.count, but you can't assert that updating the customers name will actually change the customer's name. Or, conversely, that updating a customer's name with *bad* data *doesn't* change the customer's name.

In my mind, it shouldn't really matter if the thing doing the changing is an int or a string or some complicated object. If the changing-thing responds to "==", you should be able to check if it changed. As long as you don't actually care what the exact change is that occurred - just whether or not a change *has* occurred.

So I wrote one that did.

assert_any_difference/assert_not_any_difference

Example tests. Note that you can pass a whole array of tests. These should nest, just like assert_difference

    should "not change anything if the account is active" do
      @cr = customer_records(:active)
      assert_not_any_difference ['@cr.customer_number', '@cr.is_active'] do
        assert @cr.register!
      end
    end
    should "update active status if registering a non active customer" do
      @cr = customer_records(:in_active)
      assert_not_any_difference ['@cr.customer_number'] do
        assert_any_difference ['@cr.is_active'] do
          assert @cr.register!
        end
      end
      assert @cr.active?
    end

And the assertions. You'll need to add these to test_helper.rb

  def assert_any_difference(items, &block)
    actual_diff(false, items, &block)
  end
  def assert_not_any_difference(items, &block)
    actual_diff(true, items, &block)
  end

  def actual_diff(test_if_equal, items)
    # save the old values
    old_vals = {}
    items.each {|item| old_vals[item] = eval(item) }
    # run the block
    yield
    test_fn = test_if_equal ? :assert_equal : :assert_not_equal
    # check each one
    items.each do |item|
      new_val = eval(item)
      send(test_fn, old_vals[item], new_val, "should #{'not' if test_if_equal} have been a difference for #{item}, but had: #{new_val}")
    end
  end

No comments: