Hi there,
I'm using:
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
rspec (3.1.0)
rspec-core (~> 3.1.0)
rspec-expectations (~> 3.1.0)
rspec-mocks (~> 3.1.0)
rspec-core (3.1.6)
rspec-support (~> 3.1.0)
rspec-expectations (3.1.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.1.0)
rspec-mocks (3.1.3)
rspec-support (~> 3.1.0)
rspec-rails (3.1.0)
I have this in my factory:
after(:save) do |camera|
camera.stub(:notify_agent).and_return true
end
However when I run the test, I get:
Using `stub` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` instead. Called from /Users/hackeron/Development/Xanview/timeline/spec/factories/factories.rb:18:in `block (3 levels) in <top (required)>'.
OK, so I change my factory to:
after(:save) do |camera|
allow(camera).to receive(:notify_agent).and_return true
end
After this change however I get this error:
NoMethodError:
undefined method `allow' for #<FactoryGirl::SyntaxRunner:0x007fd2c9dd0060>
Any ideas what is going wrong?
Hi @hackeron, there's actually no way to stub or spy with RSpec directly in FG callbacks like you're doing. In situations like this, typically we'd stub out whatever external services beforehand, or use a fake object to encapsulate external dependencies.
If you're running into these sorts of things with unit tests, I'd actually recommend you not use Factory Girl at all, instead relying on more traditional object instantiation to test outcomes more discretely.
There isn't? - but other than showing a deprecation warning, this works just fine:
after(:save) do |camera|
camera.stub(:notify_agent).and_return true
end
I want to use the factory as normal except I want to stub out 1 method used in a callback. This is an integration test, I still want the object to save to a database and call all the relevant callbacks, but not run just the 1 single method that hits an external service. How would I do this without causing a deprecation warning?
@hackeron because it's an integration test, I'd expect nothing to be stubbed here explicitly, at least from the instance level. After looking at the example again, camera.stub works because of how RSpec's methods are added to Object, whereas allow within the callback doesn't have the same access as RSpec (since it's the factory and not within test itself, which has different scoping/methods available).
I think the bigger question is still "Why skip one specific callback?" If it's to an external service, faking out that service would likely be a better solution.
@joshuaclayton it is an external service, but it is a connection to an external rabbitMQ server which uses some abstraction and ultimately the "amqp" gem - so this is not as simple as faking out a web service, so I just stub out the method to return true - but that shows a deprecation now.
@hackeron that makes sense - I'd still try to introduce some sort of abstraction/swappable component to change interacting with rabbit with a fake. At this point, there's nothing to address in FG specifically so I'm going to close this issue.
@hackeron If you reallllly want to just stub it you could do this:
after(:save) do |camera|
class << camera
def notify_agent; true; end
end
end
@MarkMurphy ooooh, that works beautifully, thank you!
You can add to your rails_helper.rb
FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
@printercu :+1: that works beautifully.
(Updated StackOverflow answer with this: http://stackoverflow.com/questions/9072338/should-i-stub-the-model-in-factory-girl-or-in-the-spec-file-while-testing/31129467#31129467 )
I commonly run up against this issue when trying to stub has_many associations on a model.
My instinct is to do something like this to no avail.
trait :with_bars do
after(:stub) do |foo, e|
bars = build_stubbed_list(:bar, 3)
allow(foo).to receive(:bars).and_return(bars)
end
end
But I've recently got something like this working
trait :with_bars do
after(:stub) do |foo, e|
bars = build_stubbed_list(:bar, 3)
bars.each { |bar| foo.association(:bars).add_to_target(bar) }
end
end
Just in case anyone lands here with a similar issue.
Fro those for whom include into the factory runner doesn't work you can always do this:
RSpec::Mocks::AllowanceTarget.new(target).to(
RSpec::Mocks::Matchers::Receive.new(method, proc {})
.with(...).and_return(...)
)
Most helpful comment
You can add to your
rails_helper.rb