Minitest stub passing block to mock instance - ruby

I stumbled across a curious behaviour and haven't been able to figure out what I was doing wrong. I hope somebody can enlighten me.
I was trying to stub the Redis client during my tests in a Rails application. Therefore I was using the MockRedis gem. I have created a RedisFactory class with the single class method .create, which I wanted to stub with an optional MockRedis instance like so:
def stub_redis(mock_redis = MockRedis.new)
RedisFactory.stub :create, mock_redis { yield }
end
This did not work and always threw a ArgumentError in Foo#Bar wrong number of arguments (0 for 1). Some further debugging revealed that a call of RedisFactory.create 'foo' within the stub-block resulted in an error that 'foo' is no method on instance of MockRedis::Database.
However, I have been able to solve this problem with the following code snippet, using a lambda function to catch the incoming arguments:
def stub_redis(mock_redis = MockRedis.new)
RedisFactory.stub(:create, ->(*_args) { mock_redis }) { yield }
end
Could anybody explain this behaviour?

As of now MiniTest tries to guess if the passed val_or_callable is a Proc by checking whether it responds to call, cf.:
https://apidock.com/ruby/Proc/call
https://github.com/seattlerb/minitest/blob/b84b8176930bacb4d70d6bef476b1ea0f7c94977/lib/minitest/mock.rb#L226
Unfortunately, in this specific case Redis as well as the passed MockRedis-instance both provide a generic call-method for executing Redis commands, cf.:
https://github.com/brigade/mock_redis/blob/master/lib/mock_redis.rb#L51
You already found the correct workaround. In this case, your only chance is to explicitly use the proc-version of stub.
Note: There are some communities using def call as a pattern with ServiceObjects in Ruby which may have a difficult time using minitest's stub. It is probably a good idea to open an issue in seattlerb/minitest.

Related

Rspec Double leaking to another example

I am testing a class that makes use of a client that makes external requests and I would like to mock this client, but verify that it gets called, however I am getting a double error.
My test looks like something like this:
describe '#execute' do
let(:attributes) { {foo: 'bar'} }
let(:client_double) { double('client', create: nil) }
let(:use_case) { described.class.new }
before do
allow(Client::Base).to receive(:new).and_return(client_double)
use_case.execute(attributes)
end
it 'creates something' do
expect(Something.find_by(foo: 'bar')).not_to be_nil
end
it 'calls client' do
expect(client).to have_received(:create).with('bar')
end
end
and the first example passes as expected, however rspec keeps breaking in the second example giving me this error:
#<Double "foo"> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
someone knows what I can do to fix it?
Reusing Fixtures with Let Methods
In this case, before is actually before(:each), which is reusing the client_double and attributes you defined with the #let helper method. The let commands make those variables functionally equivalent to instance variables within the scope of the described object, so you aren't really testing freshly-created objects in each example.
Some alternatives include:
Refactor to place all your setup into before(:each) without the let statements.
Make your tests DAMP by doing more setup within each example.
Set up new scope for a new #describe, so your test doubles/values aren't being reused.
Use your :before, :after, or :around blocks to reset state between tests, if needed.
Since you don't show the actual class or real code under test, it's hard to offer specific insights into the right way to test the object you're trying to test. It's not even clear why you feel you need to test the collaborator object within a unit test, so you might want to give some thought to that as well.
It turns out I was using a singleton as a client and haven't realized before, so it was trully class caching it through examples. To fix it all I did was mock the instantiate method instead of the new method and everything worked.
So in the end this worked:
allow(Client::Base).to receive(:instantiate).and_return(client_double)

How to stub method with specific parameter (and leave calls with other parameters unstubbed) in Mocha?

This question may seem like a duplicate of this one but the accepted answer does not help with my problem.
Context
Since Rails 5 no longer supports directly manipulating sessions in controller tests (which now inherit from ActionDispatch::IntegrationTest), I am going down the dark path of mocking and stubbing.
I know that this is bad practice and there are better ways to test a controller (and I do understand their move to integration tests) but I don't want to run a full integration test and call multiple actions in a single test just to set a specific session variable.
Scenario
Mocking/stubbing a session variable is actually quite easy with Mocha:
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")
Problem is, Rails stores a lot of things inside the session (just do a session.inspect anywhere in one of your views) and stubbing the :[] method obviously prevents access to any of them (so session[:some_other_variable] in a test will no longer work).
The question
Is there a way to stub/mock the :[] method only when called with a specific parameter and leave all other calls unstubbed?
I would have hoped for something like
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(:some_variable).returns("some value")
ActionDispatch::Request::Session.any_instance.stubs(:[]).with(anything).returns(original_value)
but I could not find a way to get it done.
By what I see, this is a feature not available in mocha
https://github.com/freerange/mocha/issues/334
I know this does exist in rspec-mock
https://github.com/rspec/rspec-mocks/blob/97c972be57f2c060a4a7fb8a3c5700a5ede693f0/spec/rspec/mocks/stub_implementation_spec.rb#L29
One hacky way that you an do it though, is to store the original session in an object, then mock that whenever a controller receives session, it returns another mock object, and in this you may either return a mocked velue, or delegate the call to the original session
class MySession
def initialize(original)
#original = original
end
def [](key)
if key == :mocked_key
2
else
original[key]
end
end
end
let!(original_session) { controller.send(:session) }
let(:my_session) { MySession.new(original_session) }
before do
controller.stubs(:session) { my_session }
end
Guess that mocha also allows you to do block mocking, so you don't need the class, but you need that original_session to be called
But I don't see a clean way

A better way to call methods on an instance

My question has a couple layers to it so please bear with me? I built a module that adds workflows from the Workflow gem to an instance, when you call a method on that instance. It has to be able to receive the description as a Hash or some basic data structure and then turn that into something that puts the described workflow onto the class, at run-time. So everything has to happen at run-time. It's a bit complex to explain what all the crazy requirements are for but it's still a good question, I hope. Anyways, The best I can do to be brief for a context, here, is this:
Build a class and include this module I built.
Create an instance of Your class.
Call the inject_workflow(some_workflow_description) method on the instance. It all must be dynamic.
The tricky part for me is that when I use public_send() or eval() or exec(), I still have to send some nested method calls and it seems like they use 2 different scopes, the class' and Workflow's (the gem). When someone uses the Workflow gem, they hand write these method calls in their class so it scopes everything correctly. The gem gets to have access to the class it creates methods on. The way I'm trying to do it, the user doesn't hand write the methods on the class, they get added to the class via the method shown here. So I wasn't able to get it to work using blocks because I have to do nested block calls e.g.
workflow() do # first method call
# first nested method call. can't access my scope from here
state(:state_name) do
# second nested method call. can't access my scope
event(:event_name, transitions_to: :transition_to_state)
end
end
One of the things I'm trying to do is call the Workflow#state() method n number of times, while nesting the Workflow#event(with, custom_params) 0..n times. The problem for me seems to be that I can't get the right scope when I nest the methods like that.
It works just like I'd like it to (I think...) but I'm not too sure I hit the best implementation. In fact, I think I'll probably get some strong words for what I've done. I tried using public_send() and every other thing I could find to avoid using class_eval() to no avail.
Whenever I attempted to use one of the "better" methods, I couldn't quite get the scope right and sometimes, I was invoking methods on the wrong object, altogether. So I think this is where I need the help, yeah?
This is what a few of the attempts were going for but this is more pseudo-code because I could never get this version or any like it to fly.
# Call this as soon as you can, after .new()
def inject_workflow(description)
public_send :workflow do
description[:workflow][:states].each do |state|
state.map do |name, event|
public_send name.to_sym do # nested call occurs in Workflow gem
# nested call occurs in Workflow gem
public_send :event, event[:name], transitions_to: event[:transitions_to]
end
end
end
end
end
From what I was trying, all these kinds of attempts ended up in the same result, which was my scope isn't what I need because I'm evaluating code in the Workflow gem, not in the module or user's class.
Anyways, here's my implementation. I would really appreciate it if someone could point me in the right direction!
module WorkflowFactory
# ...
def inject_workflow(description)
# Build up an array of strings that will be used to create exactly what
# you would hand-write in your class, if you wanted to use the gem.
description_string_builder = ['include Workflow', 'workflow do']
description[:workflow][:states].each do |state|
state.map do |name, state_description|
if state_description.nil? # if this is a final state...
description_string_builder << "state :#{name}"
else # because it is not a final state, add event information too.
description_string_builder.concat([
"state :#{name} do",
"event :#{state_description[:event]}, transitions_to: :#{state_description[:transitions_to]}",
"end"
])
end
end
end
description_string_builder << "end\n"
begin
# Use class_eval to run that workflow specification by
# passing it off to the workflow gem, just like you would when you use
# the gem normally. I'm pretty sure this is where everyone's head pops...
self.class.class_eval(description_string_builder.join("\n"))
define_singleton_method(:has_workflow?) { true }
rescue Exception => e
define_singleton_method(:has_workflow?) { !!(puts e.backtrace) }
end
end
end
end
# This is the class in question.
class Job
include WorkflowFactory
# ... some interesting code for your class goes here
def next!
current_state.events.#somehow choose the correct event
end
end
# and in some other place where you want your "job" to be able to use a workflow, you have something like this...
job = Job.new
job.done?
# => false
until job.done? do job.next! end
# progresses through the workflow and manages its own state awareness
I started this question off under 300000 lines of text, I swear. Thanks for hanging in there! Here's even more documentation, if you're not asleep yet.
module in my gem

RSpec any_instance return self

I'm trying to stub any instance of some class. I need to stub the fetch method, which fills the self with some data.
How can I get access to self variable, modify it and return on fetch method?
MyObject.any_instance.stub(:fetch) { self }
doesn't return a MyObject instance.
Maybe, mocks is more useful in this situation. Unfortunately, I haven't understood they yet.
There's an open rspec-mocks issue to address this. I hope to get around to addressing it at some point, but it's not simple to add this in a way that doesn't break existing spec suites that use any_instance with a block implementation, because we would start yielding an additional argument (e.g. the object instance).
Overall, any_instance can come in handy in some situations, but it's a bit of a smell, and you'll generally have fewer issues if you can find a way to mock or stub individual instances.
Here's a work around that I have not tested but should work:
orig_new = MyObject.method(:new)
MyObject.stub(:new) do |*args, &block|
orig_new.call(*args, &block).tap do |instance|
instance.stub(:fetch) { instance }
end
end
Essentially, we're simulating any_instance here by hooking into MyObject.new so that we can stub fetch on each new instance that is instantiated.
All that said, it's important to "listen to your tests", and, when something is hard to test, consider what that says about your design, rather than immediately using power tools like any_instance. Your original question doesn't give enough context for me to speculate anything about your design, but it's definitely where I would start when faced with a need to do this.
As far as I can see it, this doesn't seem to be possible, for some reason. I checked the current rspec-mocks implementation, and the method actually invoking the stub implementation seems to be the following:
# lib/rspec/mocks/message_expectation.rb:450
def call_implementation(*args, &block)
#implementation.arity == 0 ? #implementation.call(&block) : #implementation.call(*args, &block)
end
As it seems, the block is simply invoked by itself and not through instance_eval. Maybe there is another technique to achieve what you want though, after all I am not an RSpec expert by any means.

How do I stub out a specific file with rspec?

I've searched far and wide and I hope someone can answer this question. I'm using the following code to stub out the 'exists?' method for FileTest in an rspec spec:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
FileTest.stub!(:exists?).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
What I really want to do is to ensure that the existence of a very specific file is stubbed out. I was hoping something like this would do the job:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
FileTest.stub!(:exists?).with(MovieLoader.data_file).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
However, this doesn't seem to be working. I am having a very difficult time finding documentation on what the 'with' method actually does. Perhaps I'm barking up the wrong tree entirely.
Can someone please provide some guidance?
The RSpec stubbing framework leaves a bit to be desired, and this is one of those things. The stub!(:something).with("a thing") ensures that each time the something method is called that it receives "a thing" as the input. If it receives something other than "a thing", RSpec will stop the test and report an error.
I think you can achieve what you want, you'll just have to approach this a little differently. Instead of stubbing out FileTest, you should be stubbing out a method on your #loader instance, and that method would normally call FileTest.exists?. Hopefully this demonstrates what I'm getting at:
class MovieLoader
def load_file
perform_loading if file_exists?(file_path)
end
def file_exists?(path)
FileTest.exists? path
end
end
Your test would then look like:
it "returns no error if file does exist" do
#loader = MovieLoader.new
lambda {
#loader.stub!(:file_exists?).with(MovieLoader.data_file).and_return(true)
#loader.load_file
}.should_not raise_error("File Does Not Exist")
end
Now you are only stubbing one instance of the loader, so other instances will not inherit the stubbed version of file_exists?. If you need to be more fine-grained than that, you'll probably need to use a different stubbing framework, which RSpec supports (stubba, mocha, etc).

Resources