Module: AssertValueAssertion

Included in:
BeSameValueAs, MiniTest::Unit::TestCase, Minitest::Test, Test::Unit::TestCase
Defined in:
lib/assert_value.rb

Instance Method Summary collapse

Instance Method Details

#assert_value(*args) ⇒ Object

assert_value: assert which checks that two strings (expected and actual) are same and which can “magically” replace expected value with the actual in case the new behavior (and new actual value) is correct

Usage ==

Write this in the test source:

assert_value something, <<-END
    foo
    bar
    zee
END

You can also use assert_value for blocks. Then you can assert block result or raised exception

assert_value(<<-END) do
    Exception NoMethodError: undefined method `+' for nil:NilClass
END
    # Code block starts here
    c = nil + 1
end

Then run tests as usual:

rake test:units
ruby test/unit/foo_test.rb
...

When assert_value fails, you’ll be able to:

  • review diff

  • (optionally) accept new actual value (this modifies the test source file)

Additional options for test runs: –no-interactive skips all questions and just reports failures –autoaccept prints diffs and automatically accepts all new actual values –no-canonicalize turns off expected and actual value canonicalization (see below for details)

Additional options can be passed during both single test file run and rake test run:

In Ruby 1.8:
ruby test/unit/foo_test.rb -- --autoaccept
ruby test/unit/foo_test.rb -- --no-interactive

rake test TESTOPTS="-- --autoaccept"
rake test:units TESTOPTS="-- --no-canonicalize --autoaccept"

In Ruby 1.9:
ruby test/unit/foo_test.rb --autoaccept
ruby test/unit/foo_test.rb --no-interactive

rake test TESTOPTS="--autoaccept"
rake test:units TESTOPTS="--no-canonicalize --autoaccept"

Canonicalization ==

Before comparing expected and actual strings, assert_value canonicalizes both using these rules:

  • indentation is ignored (except for indentation relative to the first line of the expected/actual string)

  • ruby-style comments after “#” are ignored: both whole-line and end-of-line comments are supported

  • empty lines are ignored

  • trailing whitespaces are ignored

You can turn canonicalization off with –no-canonicalize option. This is useful when you need to regenerate expected test strings. To regenerate the whole test suite, run:

In Ruby 1.8:
rake test TESTOPTS="-- --no-canonicalize --autoaccept"
In Ruby 1.9:
rake test TESTOPTS="--no-canonicalize --autoaccept"

Example of assert_value with comments:

assert_value something, <<-END
    # some tree
    foo 1
      foo 1.1
      foo 1.2    # some node
    bar 2
      bar 2.1
END

Umportant Usage Rules ==

Restrictions:

  • only END and EOS are supported as end of string sequence

  • it’s a requirement that you have <<-END at the same line as assert_value

  • assert_value can’t be within a block

Storing expected output in files:

  • assert_value something, :log => <path_to_file>

  • path to file is relative to:

    • RAILS_ROOT (if that is defined)

    • current dir (if no RAILS_ROOT is defined)

  • file doesn’t have to exist, it will be created if necessary

Misc:

  • it’s ok to have several assert_value’s in the same test method, assert_value. correctly updates all assert_value’s in the test file

  • it’s ok to omit expected string, like this:

    assert_value something
    

    in fact, this is the preferred way to create assert_value tests - you write empty assert_value, run tests and they will fill expected values for you automatically



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/assert_value.rb', line 189

def assert_value(*args)
    if block_given?
        # rspec passes block to the expect() function, not to the matcher
        # so string substitution should work as if assert_value is called with a string
        mode = @rspec_matcher ? :scalar : :block
        expected = args[0]
        actual = ""
        begin
            actual = yield.to_s
        rescue Exception => e
            actual = "Exception #{e.class}: #{e.message}"
        end
    else
        mode = :scalar
        expected = args[1]
        actual = args[0]
    end

    if expected.nil?
        expected = ""
        change = :create_expected_string
    elsif expected.class == String
        change = :update_expected_string
    elsif expected.class == Hash
        raise ":log key is missing" unless expected.has_key? :log
        log_file = expected[:log]
        if defined? RAILS_ROOT
            log_file = File.expand_path(log_file, RAILS_ROOT)
        else
            log_file = File.expand_path(log_file, Dir.pwd)
        end
        expected = File.exist?(log_file) ? File.read(log_file) : ""
        change = :update_file_with_expected_string
    else
        internal_error("Invalid expected class #{expected.class}")
    end

    # interactive mode is turned on by default, except when
    # - --no-interactive is given
    # - CIRCLECI is set (CircleCI captures test output, but doesn't interact with user)
    # - STDIN is not a terminal device (i.e. we can't ask any questions)
    interactive = !$assert_value_options.include?("--no-interactive") && !ENV["CIRCLECI"] && STDIN.tty?
    canonicalize = !$assert_value_options.include?("--no-canonicalize")
    autoaccept = $assert_value_options.include?("--autoaccept")

    is_same_canonicalized, is_same, diff_canonicalized, diff = compare_for_assert_value(expected, actual)

    if (canonicalize and !is_same_canonicalized) or (!canonicalize and !is_same)
        diff_to_report = canonicalize ? diff_canonicalized : diff
        if interactive
            # print method name and short backtrace
            soft_fail(diff_to_report)

            if autoaccept
                accept = true
            else
                print "Accept the new value: yes to all, no to all, yes, no? [Y/N/y/n] (y): "
                STDOUT.flush
                response = STDIN.gets.strip
                accept = ["", "y", "Y"].include? response
                $assert_value_options << "--autoaccept" if response == "Y"
                $assert_value_options << "--no-interactive" if response == "N"
            end

            if accept
                if [:create_expected_string, :update_expected_string].include? change
                    accept_string(actual, change, mode)
                elsif change == :update_file_with_expected_string
                    accept_file(actual, log_file)
                else
                    internal_error("Invalid change #{change}")
                end
            end
        end
        if accept
            # when change is accepted, we should not report it as a failure because
            # we want the test method to continue executing (in case there're more
            # assert_value's in the method)
            succeed
        else
            fail(diff)
        end
    else
        succeed
    end
end

#be_same_value_as(expected = nil) ⇒ Object



276
277
278
# File 'lib/assert_value.rb', line 276

def be_same_value_as(expected = nil)
  BeSameValueAs.new(expected)
end

#file_offsetsObject



83
84
85
# File 'lib/assert_value.rb', line 83

def file_offsets
  @@file_offsets ||= Hash.new { |hash, key| hash[key] = {} }
end