New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop using Mocha #33162

Merged
merged 3 commits into from Aug 22, 2018

Conversation

Projects
None yet
6 participants
@utilum
Contributor

utilum commented Jun 19, 2018

Following discussion with @bogdanvlviv I'm breaking this up into smaller manageable more review-friendly PRs, that will be submitted one by one.

From the lowest hanging fruit to deep embeded mocking.

  • Remove unnecessary mock objects (created with by .stubs) 8c1e172, 2af162c, d2d7296
    Working on these tests it became evident that some calls to Mocha#stub and Mocha#stubs do not serve the test case in any way. The same is true of some calls to returns on such objects. Remove both.
  • Reduce mocking by testing for value instead of method call 331cca1
    Another low hanging fruit. When we can test for value, we do not need to test the setter method was called on a mock.
  • Replace shallow mocks with Ruby classes f7bfb3d
    Some mocks are simple to replace with a minimal class that responds to the needed methods.
  • Replace Mocha#stubs with `Minitest#stub 837d603, 7afdc6c, a94d6ae
  • Use MethodCallAssertions instead of Mocha part 1 d0743d0, d899375
    Replace simple instances of Mocha#expect with MethodCallAssertions.
  • Use MethodCallAssertions instead of Mocha part 2 82e42c1
    Replace instances of Mocha#expects with MethodCallAssertsions where expecations need to be added to withstand Minitest's more stringent mocking.
  • Stub deeper mocks with new MethodCallAssertions
    Remaining calls to Mocha#stubs and Mocha#expects cannot be replicated with plain Minitest. Stub and mock them with Ruby.
  • Remove mocha from Gemfile [Q.E.F.]
@rails-bot

This comment has been minimized.

rails-bot commented Jun 19, 2018

r? @kamipo

(@rails-bot has picked a reviewer for you, use r? to override)

@utilum utilum changed the title from Stop using mocha to Stop using Mocha Jun 20, 2018

@bogdanvlviv

Hi @utilum. Thanks for working on this.
I've done first look at the PR and have some concerns I've mentioned below.
Also, What do you think about splitting this PR to small PRs? It will be easier to review and catch some not explicit mistakes if exists.

activerecord/test/cases/adapters/mysql2/active_schema_test.rb Outdated
@@ -157,18 +157,36 @@ def test_remove_timestamps
end
def test_indexes_in_create
ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
with_stubbed_source_index_name_exist do

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

Currently this test doesn't test anything.
Could you apply this diff

diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index bbc9823536..202856907a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -170,21 +170,23 @@ def test_indexes_in_create
   private

     def with_stubbed_source_index_name_exist
-      def (ActiveRecord::Base.connection).data_source_exists?(*)
-        unless arg == :temp
+      def (ActiveRecord::Base.connection).data_source_exists?(name)
+        unless name == :temp
           raise "Expected #data_source_exists? to be called with :temp"
         end

         false
       end

-      def (ActiveRecord::Base.connection).index_name_exists?(*)
-        unless arg == :index_temp_on_zip
+      def (ActiveRecord::Base.connection).index_name_exists?(name)
+        unless name == :index_temp_on_zip
           raise "Expected #index_name_exists? to be called with :index_temp_on_zip"
         end

         false
       end
+
+      yield
     end

     def with_real_execute
activerecord/test/cases/fixtures_test.rb Outdated
connection = Class.new do
attr_accessor :pool
def transaction_open?; true; end

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

I think it should be false since it was stub(transaction_open?: false)

activerecord/test/cases/fixtures_test.rb Outdated
end.new
connection.pool = Class.new do
def lock_thread=(lock_thread); false; end

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

Why does it should return false?

activerecord/test/cases/tasks/database_tasks_test.rb Outdated
assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
ActiveRecord::Tasks::DatabaseTasks.create_all
with_stubbed_configuration_establish_connection do
$stderr.stub(:puts, nil) do

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

This stub seems extra

activerecord/test/cases/tasks/database_tasks_test.rb Outdated
end
def with_stubbed_configurations_establish_connection
ActiveRecord::Base.stub(:configurations, @configurations) do
ActiveRecord::Base.stub(:establish_connection, nil) do

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

Previously it was like ActiveRecord::Base.stubs(:establish_connection).returns(true) so i think we should pass true here.

activerecord/test/cases/tasks/postgresql_rake_test.rb Outdated
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
with_stubbed_method(ActiveRecord::Base.singleton_class,
:establish_connection, true) do

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

can we use with_stubbed_connection_establish_connection here?

activerecord/test/cases/tasks/postgresql_rake_test.rb Outdated
@connection = stub(create_database: true, drop_database: true)
@connection = Class.new do
def create_database(*); end
def drop_database(*); end

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

shouldn't these methods return true as it was previously?

activerecord/test/support/method_stub.rb Outdated
def unstub_method(object, method_name)
object.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
alias_method(method_name, "original_#{method_name}")
remove_method("stubbed_#{method_name}")

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

i would also add remove_method("original_#{method_name}"). Also would be great to remove
@@#{base_var_name}_called and @@#{base_var_name}_args somehow

This comment has been minimized.

@utilum

utilum Jul 22, 2018

Contributor

I'm adding remove_method as you suggest, and remove_class_variable for the variables.

activerecord/test/support/method_stub.rb Outdated
define_method("stubbed_#{method_name}") do |*args|
eval "@@#{base_var_name}_called += 1"
eval "@@#{base_var_name}_args += args"

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

is there need to use eval

This comment has been minimized.

@utilum

utilum Jul 8, 2018

Contributor

How else will we define these class variables dynamically? With send ?

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 8, 2018

Contributor

I guess using of just @@#{base_var_name}_called += 1 should work as well.
And what about using of instance variabes instead of class variables then we are able to use remove_instance_variable http://ruby-doc.org/core-2.5.1/Object.html#method-i-remove_instance_variable

This comment has been minimized.

@utilum

utilum Jul 8, 2018

Contributor

You are right! Removing the redundant evals.

activerecord/test/support/method_stub.rb Outdated
module MethodStub
private
def with_stubbed_method(object, method_name, return_value = nil)

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 6, 2018

Contributor

what do you think about renaming this to with_stubbed_instance_method since it stubs an instance method.
Also rename MethodStub InstanceMethodStub and name of the file to instance_method_stub.rb for consistency

This comment has been minimized.

@utilum

utilum Jul 8, 2018

Contributor

I'm not sure this would be accurate, what about when we pass SomeObject.singleton_class as the object argument?

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Jul 8, 2018

Contributor

Currently, I have no the right answer. We might discuss it later on new short Pull Request
as I mentioned:

Would be great to see simple PR with https://github.com/rails/rails/pull/33162/files#diff-81e98371a331e686a926985a7b9e9d88 activerecord/test/support/method_stub.rb file. It might help concentrate more people on it and get more suggestions with possible improvements to it.

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 6, 2018

Thanks a lot @bogdanvlviv. I was beginning to think of breaking it into smaller PRs too, wondering if it's too bulky to be reviewed. No problem with that. I'll start working on your specific comments first, and perhaps garner some more input by the time they are answered.

@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Jul 6, 2018

To be honest It took me about 3 hours to review 85% of PR. 😅.
(I can't imagine how much time it took you to prepare this PR. Thank you for that 🙇 )
I think if we go with small PR, everyone will be more confident in changes we merge and It can prevent merging not explicit mistakes.

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 6, 2018

Yeah... took me a good few days. Sometimes that's what's needed, to dive into the code.
And I see what you mean. Thank you for taking the time. I had thought that breaking into a logical progression of commits, each passing tests and advancing us toward the goal would facilitate review, but I guess it did not have the desired effect.
So... how about I change the description of this PR into a TODO list for smaller PRs, submit those, and check items as they are completed? This would conserve your comments as well.

@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Jul 6, 2018

I would recommend don't submit all PRs at once, instead go one by one(I mean open new one if previous one was merged).
Would be great to see simple PR with https://github.com/rails/rails/pull/33162/files#diff-81e98371a331e686a926985a7b9e9d88 activerecord/test/support/method_stub.rb file. It might help concentrate more people on it and get more suggestions with possible improvements to it.

This would conserve your comments as well.

Please don't worry I'll be happy to review again.

@utilum utilum changed the title from Stop using Mocha to WIP: Stop using Mocha Jul 6, 2018

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 6, 2018

Started acting on your suggestion @bogdanvlviv. I'm leaving this PR here for now, as a reference, and also, should the smaller PRs all work out, we'd have an easy reference for what connects them all.

I would recommend don't submit all PRs at once, instead go one by one(I mean open new one if previous one was merged).

They'd need to be submitted one by one, as for the most part each commit was based on the ones that precede it.

@kaspth

This comment has been minimized.

Member

kaspth commented Jul 7, 2018

I was just looking into reviewing this the other day. Then I skimmed the diff and thought "nope! not today". So 👍 on the smaller PRs.

I remember originally we wanted to replace Mocha because it took 300 ms to load, which was a pain for the railties tests as they fork a new process for each test or so. Is that still relevant? 😊

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 7, 2018

I am not sure how long it takes, and failed to find the discussion that preceded all those # FIXME: stop using mocha comments. Any pointers?
What did motivate me is making the tests more precise, with Minitest's strict mocks and stubs. Avoiding such lines as SomeClass.any_instance.expects(:some_method).with(:some_arg).never, as facilitated by Mohca.

utilum added a commit to utilum/rails that referenced this pull request Jul 7, 2018

Remove unnecessary Mocha stubs
Step 1 in rails#33162

[utilum + bogdanvlviv]

utilum added a commit to utilum/rails that referenced this pull request Jul 9, 2018

utilum added a commit to utilum/rails that referenced this pull request Jul 10, 2018

Replace shallow mocks with Ruby classes
While preparing this I realised that some stubbed returns values
serve no purpose, so this patch drops those as well.

Step 3 in rails#33162

bogdanvlviv added a commit to bogdanvlviv/rails that referenced this pull request Jul 10, 2018

Fix stubbed methods in test cases
Remove returning of `false` value for stubbed `lock_thread=` methods
since there aren't any needs in it.

Remove unnecessary returning of `true` for stubbed `drop_database` method.
Follow up rails#33309.

Related to rails#33162, rails#33326.
@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Jul 30, 2018

@kaspth I've taken look into it:
The first issue was related to using of undef_method, we should use remove_method in our case, see info about these methods in Ruby docs http://ruby-doc.org/core-2.5.1/Module.html#method-i-undef_method.
The second issue related to method definition - we probably should use something like define_method(method_name) {|*|} instead of define_method(method_name) {}.

But I have some doubt about this implementation since it doesn't remove a module from ancestors after execution of assert_called_on_instance_of/assert_not_called_on_instance_of
Example:

3.times do
  assert_not_called_on_instance_of(Level, :increment) do
  end
end
Level.ancestors #=> [#<Module:0x000055fbc736bd20>, #<Module:0x000055fbc736be60>, #<Module:0x000055fbc6d29818>, Level, Object, ActiveSupport::Tryable, Kernel, BasicObject]

Maybe would be better to go with using of the class_eval as it is in #33162 (comment)?

@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Jul 31, 2018

@kaspth How about

def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
  aliased_method, punctuation = method_name.to_s.sub(/([?!=])$/, ""), $1
  with_method = "#{aliased_method}_with_stub#{punctuation}"
  without_method = "#{aliased_method}_without_stub#{punctuation}"

  times_called = 0

  klass.send(:define_method, with_method) do |*|
    times_called += 1

    returns
  end

  klass.send(:alias_method, without_method, method_name)
  klass.send(:alias_method, method_name, with_method)

  case
  when klass.protected_method_defined?(without_method)
    klass.send(:protected, method_name)
  when klass.private_method_defined?(without_method)
    klass.send(:private, method_name)
  end

  yield

  error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
  error = "#{message}.\n#{error}" if message

  assert_equal times, times_called, error
ensure
  klass.send(:alias_method, method_name, without_method)
  klass.send(:remove_method, with_method)
  klass.send(:remove_method, without_method)
end

def assert_not_called_on_instance_of(klass, method_name, message = nil, &block)
  assert_called_on_instance_of(klass, method_name, message, times: 0, &block)
end

Implementation is similar to #33325, and #33162 (comment).

I also updated the diff in #33162 (comment).

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 31, 2018

I agree with @bogdanvlviv , a module prepended is a pain to unpend, and while supporting Ruby 2.4 we'd still be forced to #send all of define_method, alias_method, and method_remove. Added a comment to revisit this.

SO: I've moved the stubbing to MethodCallAssertions, changed its implementation to ressemble assert_called, and added @bogdanvlviv 's tests verbatim.

🍬 collaborating with you guys.

@kaspth

This comment has been minimized.

Member

kaspth commented Jul 31, 2018

@bogdanvlviv I like that better, though I don't see the need for the private/protected finagling.

I also don't see the problem with the module prepend. It's an internal method for sparing use. It's much better that it's fewer lines of easier to understand code.

So, why bother unprepending the module? I could shift on that if there were specific failures because of it when you tested @bogdanvlviv?

🍬 collaborating with you guys.

Feeling's mutual! ❤️

@utilum

This comment has been minimized.

Contributor

utilum commented Jul 31, 2018

  1. We need to send as in Ruby 2.4 undef_method as well as remove_method are private, example (out of 3 failures, 7 errors):
MethodCallAssertionsTest#test_assert_called_on_instance_of_with_arguments:
NoMethodError: private method `remove_method' called for #<Module:0x000056467b2f6fd0
  1. We need to recreate the original method, or we end up missing it, example (out of 3 failures, 7 errors):
Error:
MethodCallAssertionsTest#test_assert_called_on_instance_of_nesting:
NameError: undefined method `increment' for module `#<Module:0x000055dda3ecc6c0>'

After fixing these two issues, the legibility gained by prepending a module is not obvious.

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 1, 2018

The second commit, ed537c3 in this PR is now rebased and includes the tested stubbing in ActiveSupport::Testing::MethodCallAssertions.

For the reasons given just above, it does not use a prepended module. @kaspth do you think we can do the same and have easier to read code by prepending a module?

I've also added a couple of tests to verfiy that we can stub a method that does not exist, and that said introduced method does does not stay defined for the klass after the stubbing op.

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 3, 2018

Here's how a prepended module may look:

        def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
          mod = Module.new
          klass.prepend mod

          times_called = 0

          if klass.respond_to?(method_name)
            mod.send(:alias_method, "original_#{method_name}", method_name)
          end

          mod.send(:define_method, method_name) do |*|
            times_called += 1

            returns
          end

          yield

          error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times"
          error = "#{message}.\n#{error}" if message

          assert_equal times, times_called, error
        ensure
          if mod.method_defined?("original_#{method_name}")
            mod.send(:alias_method, method_name, "original_#{method_name}")
            mod.send(:remove_method, "original_#{method_name}")
          else
            mod.send(:remove_method, method_name)
          end
        end

It's a wee longer. Is it easier on the eye?
What am I missing?

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 12, 2018

Rebased.
Is there anything I can do to advance this?

@kaspth

All right, got around to seeing why my prepended module version didn't work. Thanks guys!

As another crazy attempt I tried to see if I could override this https://github.com/seattlerb/minitest/blob/e6bc4485730403faff6966c1671cf5de72b2d233/lib/minitest/mock.rb#L215 somehow via a wrapper class. Because then we could just use assert_called.

Let's mimick minitests aliasing and undef at the least. I think the method defined check in ensure is too much.

Pretty close now! ❤️

activesupport/lib/active_support/testing/method_call_assertions.rb Outdated
message,
times: 0,
&block
)

This comment has been minimized.

@kaspth

kaspth Aug 12, 2018

Member

Let's just keep these arguments on one line, here and elsewhere. Same for the method definition.

This comment has been minimized.

@utilum

utilum Aug 12, 2018

Contributor

Fixed.

@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Aug 12, 2018

Isn't #33162 (comment) simple enough? We use the same implementation in deprecate_methods, see #33325.

activesupport/lib/active_support/testing/method_call_assertions.rb Outdated
@@ -35,6 +35,53 @@ def assert_not_called(object, method_name, message = nil, &block)
assert_called(object, method_name, message, times: 0, &block)
end
# TODO: No need to resort to #send once support for Ruby 2.4 is
# dropped. May even use prepended module. See:
# https://github.com/rails/rails/pull/33162#issuecomment-408616175 .

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Aug 12, 2018

Contributor

I think we have a lot of places where we use send because of the same reason in Rails code base, and I'm not sure whether there are any benefits/reason of changing it in the future.

This comment has been minimized.

@utilum

utilum Aug 12, 2018

Contributor

There is no need to keep method calls that were only introduced to support a version of Ruby once support for that version is dropped. Unnecessary method calls are not a good idea either for performance code clarity.
I have not checked the other places, but it may be a good idea to check when the time comes.

@kaspth

This comment has been minimized.

Member

kaspth commented Aug 12, 2018

@bogdanvlviv sure, we can go with that. I'd cut the protected/private stuff and use prefixes, so we don't have to care about the punctuation though.

utilum added some commits Jul 23, 2018

Add method_call_assertions and use them instead of Mocha
Six Mocha calls prove quite resistant to Minitestification. For example,
if we replace

```
  ActiveRecord::Associations::HasManyAssociation
    .any_instance
    .expects(:reader)
    .never
```

with `assert_not_called`, Minitest wisely raises

```
NameError: undefined method `reader' for class `ActiveRecord::Associations::HasManyAssociation'
```

as `:reader` comes from a deeply embedded abstract class,
`ActiveRecord::Associations::CollectionAssociation`.

This patch tackles this difficulty by adding
`ActiveSupport::Testing::MethodCallAsserts#assert_called_on_instance_of`
which injects a stubbed method into `klass`, and verifies the number of
times it is called, similar to `assert_called`. It also adds  a convenience
method, `assert_not_called_on_instance_of`, mirroring
`assert_not_called`.

It uses the new method_call_assertions to replace the remaining Mocha
calls in `ActiveRecord` tests.

[utilum + bogdanvlviv + kspath]
@utilum

This comment has been minimized.

Contributor

utilum commented Aug 13, 2018

OK, I've removed the checks for method_defined?, and removed the tetss for an introduced method. Now closer to mimicking Minitest's aliasing and undef more closely.

ensure
klass.send(:alias_method, method_name, "original_#{method_name}")
klass.send(:undef_method, "original_#{method_name}")
klass.send(:undef_method, "stubbed_#{method_name}")

This comment has been minimized.

@bogdanvlviv

This comment has been minimized.

@utilum

utilum Aug 13, 2018

Contributor

Why would we want Ruby to "still search superclasses and mixed-in modules for a possible receiver" of "original_#{method_name}" or "stubbed_#{method_name}"?

This was a valid point when we removed method_name, but that's no longer the case.

This comment has been minimized.

@bogdanvlviv

bogdanvlviv Aug 13, 2018

Contributor

Maybe we shouldn't since using of undef_method only caused the issue in #33162 (comment), #33162 (comment)

This comment has been minimized.

@utilum

utilum Aug 13, 2018

Contributor

I don't see that it causes any issue.

@bogdanvlviv

Excellent! Thanks for working on it.

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 15, 2018

I'm not happy with losing the check for method_defined?.
Now, if we try to stub a method that's not defined, we get a confusing error message.

def test_assert_called_on_instance_of_introduced_method_failure
  assert_called_on_instance_of Level, :introduced_method do
    @object.introduced_method
  end
end

output:

Error:
MethodCallAssertionsTest#test_assert_called_on_instance_of_introduced_method_failure:
NameError: undefined method `original_introduced_method' for class `MethodCallAssertionsTest::Level'
    /home/u/code/rails/activesupport/lib/active_support/testing/method_call_assertions.rb:59:in `alias_method'
    /home/u/code/rails/activesupport/lib/active_support/testing/method_call_assertions.rb:59:in `ensure in assert_called_on_instance_of'
    /home/u/code/rails/activesupport/lib/active_support/testing/method_call_assertions.rb:61:in `assert_called_on_instance_of'
    /home/u/code/rails/activesupport/test/testing/method_call_assertions_test.rb:165:in `test_assert_called_on_instance_of_introduced_method_failure'

Don't we care about this case?

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 15, 2018

We could add something like raise(NameError, "undefined method '#{method_name}' for class #{klass}") unless klass.method_defined?("original_#{method_name}") to the ensure block to make this clear.

@bogdanvlviv

This comment has been minimized.

Contributor

bogdanvlviv commented Aug 21, 2018

Since this for internal use only, I think it is fine as it is.

@kaspth kaspth merged commit 9136bb7 into rails:master Aug 22, 2018

2 checks passed

codeclimate All good!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@kaspth

This comment has been minimized.

Member

kaspth commented Aug 22, 2018

Thanks so much! It's been a pleasure working with both of you ❤️

@utilum

This comment has been minimized.

Contributor

utilum commented Aug 22, 2018

@kaspth, just to be sure, are you fine with the odd error message we'd now return if trying to stub a method that does not exist?

@kaspth

This comment has been minimized.

Member

kaspth commented Aug 22, 2018

@utilum yeah, it's a method for internal use. Let's see if we get hit by it 😊

@utilum utilum deleted the utilum:stop_using_mocha branch Aug 22, 2018

@vbrazo

This comment has been minimized.

Contributor

vbrazo commented Aug 28, 2018

Good job @utilum

bogdanvlviv added a commit to bogdanvlviv/minitest-mock_expectations that referenced this pull request Oct 18, 2018

Initial commit
Inspired by https://github.com/rails/rails/blob/master/activesupport/lib/active_support/testing/method_call_assertions.rb

I found it useful and this can help omit using of gems like https://github.com/freerange/mocha.
See this example of replcaing Mocha gem by simple minitest's mocks and assertions: rails/rails#33162.

@bogdanvlviv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment