{"id":18319770,"url":"https://github.com/redding/much-stub","last_synced_at":"2025-04-05T22:31:35.705Z","repository":{"id":44801244,"uuid":"136226903","full_name":"redding/much-stub","owner":"redding","description":"Stubbing API for replacing method calls on objects in test runs.","archived":false,"fork":false,"pushed_at":"2022-01-23T20:41:03.000Z","size":100,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-21T13:12:33.063Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/redding.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-05T19:45:32.000Z","updated_at":"2025-01-22T03:48:05.000Z","dependencies_parsed_at":"2022-07-21T10:02:21.204Z","dependency_job_id":null,"html_url":"https://github.com/redding/much-stub","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fmuch-stub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fmuch-stub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fmuch-stub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redding%2Fmuch-stub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redding","download_url":"https://codeload.github.com/redding/much-stub/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247411236,"owners_count":20934650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T18:14:12.187Z","updated_at":"2025-04-05T22:31:35.460Z","avatar_url":"https://github.com/redding.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MuchStub\n\nMuchStub is a stubbing API for replacing method calls on objects in test runs.  This is intended to be brought into testing environments and used in test runs to stub out external dependencies.\n\nAll it does is replace method calls.  In general it tries to be friendly and complain if stubbing doesn't match up with the object/method being stubbed:\n\n* each stub takes a block that is called in place of the method\n* complains if you stub a method that the object doesn't respond to\n* complains if you stub with an arity mismatch\n* no methods are added to `Object` to support stubbing\n\nNote: this was originally implemented in and extracted from [Assert](https://github.com/redding/assert).\n\n## Usage\n\n```ruby\n# Given this object/API\n\nmy_class =\n  Class.new do\n    def my_method\n      \"my-method\"\n    end\n\n    def my_value(value)\n      value\n    end\n  end\nmy_object = my_class.new\n\nmy_object.my_method\n  # =\u003e \"my-method\"\nmy_object.my_value(123)\n  # =\u003e 123\nmy_object.my_value(456)\n  # =\u003e 456\n\n# Create a new stub for the :my_method method\n\nMuchStub.(my_object, :my_method)\nmy_object.my_method\n  # =\u003e StubError: `my_method` not stubbed.\nMuchStub.(my_object, :my_method){ \"stubbed-method\" }\nmy_object.my_method\n  # =\u003e \"stubbed-method\"\nmy_object.my_method(123)\n  # =\u003e StubError: arity mismatch\nMuchStub.(my_object, :my_method).with(123){ \"stubbed-method\" }\n  # =\u003e StubError: arity mismatch\n\n# Call the original method after it has been stubbed.\n\nMuchStub.stub_send(my_object, :my_method)\n  # =\u003e \"my-method\"\n\n# Create a new stub for the :my_value method\n\nMuchStub.(my_object, :my_value){ \"stubbed-method\" }\n  # =\u003e StubError: arity mismatch\nMuchStub.(my_object, :my_value).with(123){ |val| val.to_s }\nmy_object.my_value\n  # =\u003e StubError: arity mismatch\nmy_object.my_value(123)\n  # =\u003e \"123\"\nmy_object.my_value(456)\n  # =\u003e StubError: `my_value(456)` not stubbed.\n\n# Call the original method after it has been stubbed.\n\nMuchStub.stub_send(my_object, :my_value, 123)\n  # =\u003e 123\nMuchStub.stub_send(my_object, :my_value, 456)\n  # =\u003e 456\n\n# Unstub individual stubs\n\nMuchStub.unstub(my_object, :my_method)\nMuchStub.unstub(my_object, :my_value)\n\n# OR blanket unstub all stubs\n\nMuchStub.unstub!\n\n# The original API/behavior is preserved after unstubbing\n\nmy_object.my_method\n  # =\u003e \"my-method\"\nmy_object.my_value(123)\n  # =\u003e 123\nmy_object.my_value(456)\n  # =\u003e 456\n```\n\n### Stubs for spying\n\n```ruby\n# Given this object/API\n\nmy_class =\n  Class.new do\n    def basic_method(value)\n      value\n    end\n\n    def iterator_method(items, \u0026block)\n      items.each(\u0026block)\n    end\n  end\nmy_object = my_class.new\n\n# Store method call arguments/blocks for spying.\n\nbasic_method_called_with = nil\nMuchStub.(my_object, :basic_method) { |*args|\n  basic_method_called_with = MuchStub::Call.new(*args)\n}\n# OR\nMuchStub.(my_object, :basic_method).on_call { |call|\n  basic_method_called_with = call\n}\n# OR\nMuchStub.on_call(my_object, :basic_method) { |call|\n  # MucStub.on_call(...) { ... } is equivalent to\n  # MuchStub.(...).on_call { ... }\n  basic_method_called_with = call\n}\n\nmy_object.basic_method(123)\nbasic_method_called_with.args\n  # =\u003e [123]\n\nbasic_method_called_with = nil\nMuchStub.(my_object, :basic_method).with(4, 5, 6) { |*args|\n  basic_method_called_with = MuchStub::Call.new(*args)\n}\n# OR\nMuchStub.(my_object, :basic_method).with(4, 5, 6).on_call { |call|\n  basic_method_called_with = call\n}\n\nmy_object.basic_method(4, 5, 6)\nbasic_method_called_with.args\n  # =\u003e [4,5,6]\n\niterator_method_called_with = nil\nMuchStub.(my_object, :iterator_method) { |*args, \u0026block|\n  iterator_method_called_with = MuchStub::Call.new(*args)\n}\n# OR\nMuchStub.(my_object, :iterator_method).on_call { |call|\n  iterator_method_called_with = call\n}\n\nmy_object.iterator_method([1, 2, 3], \u0026:to_s)\niterator_method_called_with.args\n  # =\u003e [[1, 2, 3]]\niterator_method_called_with.block\n  # =\u003e #\u003cProc:0x00007fb083a6feb0(\u0026:to_s)\u003e\n\n# Count method calls for spying.\n\nbasic_method_call_count = 0\nMuchStub.(my_object, :basic_method) {\n  basic_method_call_count += 1\n}\n\nmy_object.basic_method(123)\nbasic_method_call_count\n  # =\u003e 1\n\n# Count method calls and store arguments for spying.\n\nbasic_method_calls = []\nMuchStub.(my_object, :basic_method) { |*args|\n  basic_method_calls \u003c\u003c MuchStub::Call.new(*args)\n}\n# OR\nMuchStub.(my_object, :basic_method).on_call { |call|\n  basic_method_calls \u003c\u003c call\n}\n\nmy_object.basic_method(123)\nbasic_method_calls.size\n  # =\u003e 1\nbasic_method_calls.first.args\n  # =\u003e [123]\n```\n\n### Stubs for test doubles.\n\n```ruby\n# Given this object/API ...\n\nmy_class =\n  Class.new do\n    def build_thing(thing_value);\n      Thing.new(value)\n    end\n  end\nmy_object = my_class.new\n\n# ... and this Test Double.\nclass FakeThing\n  attr_reader :built_with\n\n  def initialize(*args)\n    @built_with = args\n  end\nend\n\n# Stub in the test double.\n\nMuchStub.(my_object, :build_thing) { |*args|\n  FakeThing.new(*args)\n}\n\nthing = my_object.build_thing(123)\nthing.built_with\n  # =\u003e [123]\n```\n\n### `MuchStub.tap`\n\nUse the `.tap` method to spy on method calls while preserving the original method return value and behavior.\n\n```ruby\n# Given this object/API\n\nmy_class =\n  Class.new do\n    def basic_method(value)\n      value.to_s\n    end\n  end\nmy_object = my_class.new\n\n# Normal stubs override the original behavior and return value...\nbasic_method_called_with = nil\nMuchStub.(my_object, :basic_method) { |*args|\n  basic_method_called_with = args\n}\n\n# ... in this case not converting the value to a String and returning it and\n# instead returning the arguments passed to the method.\nmy_object.basic_method(123)\n  # =\u003e [123]\nbasic_method_called_with\n  # =\u003e [123]\n\n# Use `MuchStub.tap` to preserve the methods behavior and also spy.\n\nbasic_method_called_with = nil\nMuchStub.tap(my_object, :basic_method) { |value, *args|\n  basic_method_called_with = MuchStub::Call.new(*args)\n}\n# OR\nMuchStub.tap_on_call(my_object, :basic_method) { |value, call|\n  basic_method_called_with = call\n}\n\nmy_object.basic_method(123)\n  # =\u003e \"123\"\nbasic_method_called_with.args\n  # =\u003e [123]\n```\n\n#### Late-bound stubs using `MuchStub.tap`\n\nUse the `.tap` method to stub any return values of method calls.\n\n```ruby\n# Given:\n\nclass Thing\n  attr_reader :value\n\n  def initialize(value)\n    @value = value\n  end\nend\n\nmy_class =\n  Class.new do\n    def thing(value)\n      Thing.new(value)\n    end\n  end\nmy_object = my_class.new\n\n# Use `MuchStub.tap` to stub any thing instances created by `my_object.thing`\n# (and also spy on the call arguments)\n\nthing_built_with = nil\nMuchStub.tap(my_object, :thing) { |thing, *args|\n  thing_built_with = args\n  MuchStub.(thing, :value) { 456 }\n}\n\nthing = my_object.thing(123)\n  # =\u003e #\u003cThing:0x00007fd5ca9df510 @value=123\u003e\nthing_built_with\n  # =\u003e [123]\nthing.value\n  # =\u003e 456\n```\n\n### `MuchStub.spy`\n\nUse the `.spy` method to spy on method calls. This is especially helpful for spying on _chained_ method calls.\n\n```ruby\n# Given this object/API\n\nmyclass =\n  Class.new do\n    def one; self; end\n    def two(val); self; end\n    def three; self; end\n    def ready?; false; end\n  end\nmyobj = myclass.new\n\nspy = MuchStub.spy(myobj :one, :two, :three, ready?: true)\n\nassert_equal spy, myobj.one\nassert_equal spy, myobj.two(\"a\")\nassert_equal spy, myobj.three\n\nassert_true myobj.one.two(\"b\").three.ready?\n\nassert_kind_of MuchStub::CallSpy, spy\nassert_equal 2, spy.one_call_count\nassert_equal 2, spy.two_call_count\nassert_equal 2, spy.three_call_count\nassert_equal 1, spy.ready_predicate_call_count\nassert_equal [\"b\"], spy.two_last_called_with.args\nassert_true spy.ready_predicate_called?\n```\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem \"much-stub\"\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install much-stub\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am \"Added some feature\"`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredding%2Fmuch-stub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredding%2Fmuch-stub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredding%2Fmuch-stub/lists"}