25

Almost every spec file I come accross I end up writing stuff like:

  before :each do
    @cimg = Factory.build :cimg_valid
    @cimg.stub(:validate_img).and_return true
    @cimg.stub(:validate_img_url).and_return true
    @cimg.stub(:save_images).and_return true
    @cimg.stub(:process_image).and_return true
    @cimg.stub(:img).and_return true
  end

I mean, the model I get from Factory.build is completely valid. But if I don't stub that stuff it saves things in the filesystem, and validates stuff I'm not testing...

What I mean, I think it would be cleaner to do something like this:

  before :each do
    @cimg = Factory.build :cimg_for_testing_tags
  end

If stubbing within the Factory is even possible.

What is the proper way to stub the model?

Zequez
  • 3,399
  • 2
  • 31
  • 42

4 Answers4

35

@fkreusch's answer works great until you use the new RSpec expect() syntax (3.0+)

Putting this into rails_helper.rb works for me:

FactoryBot::SyntaxRunner.class_eval do
  include RSpec::Mocks::ExampleMethods
end

In the OP's example, you can now do:

FactoryBot.define do
  factory :cimg_for_testing_tags do

    ... # Factory attributes

    after(:build) do |cimg|
      allow(cimg).to receive(:validate_img) { true }
    end
  end
end

Credit: github.com/printercu, see: https://github.com/thoughtbot/factory_bot/issues/703#issuecomment-83960003

Qortex
  • 7,087
  • 3
  • 42
  • 59
Ho-Sheng Hsiao
  • 1,327
  • 12
  • 10
  • 5
    Doing what is described here now issues the following error (FactoryGirl v4.7.0): ```The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported. ``` – jsears Sep 29 '16 at 17:20
  • @jsears I was encountering that error because my `rails_helper.rb` was running `FactoryGirl.lint` in a `before(:suite)` block, and stubs can't be set up there. I solved the issue by wrapping the lint call in `RSpec::Mocks.with_temporary_scope { ... }`. – vergenzt Nov 28 '16 at 22:22
  • 1
    Note that `FactoryGirl` is now `FactoryBot`, so those two names should change in your example. – aardvarkk Dec 14 '17 at 00:00
22

In recent versions of factory_girl you have an after_build callback, so I believe you could define your factory like this:

FactoryGirl.define do
  factory :cimg_for_testing_tags do

    ... # Factory attributes

    after_build do |cimg|
      cimg.stub(:validate_img).and_return true
    end
  end
end

UPDATE

After factory_girl 3.3.0, the syntax has changed to following:

FactoryGirl.define do
  factory :cimg_for_testing_tags do

    ... # Factory attributes

    after(:build) do |cimg|
      cimg.stub(:validate_img).and_return true
    end
  end
end
lulalala
  • 17,572
  • 15
  • 110
  • 169
fkreusch
  • 1,329
  • 11
  • 14
  • 3
    But *should I do it*? Or should I stub it in the spec file? – Zequez Jan 31 '12 at 01:24
  • 2
    I think that if you are repeating yourself lots of times, having a general stub make sense in this case, just make sure that :cimg_for_testing_tags has a parent factory that is not stubbed for situations in which you want to test the actual behavior. – fkreusch Jan 31 '12 at 11:08
  • This is an awesome technique. I've found myself struggling with a solution for this kind of situations. Thanks @fkreusch ! – josemota May 11 '12 at 19:32
  • 6
    This no longer works. As of the latest rspec this will result in the failure `"Using 'stub' from rspec-mocks' old ':should' syntax without explicitly enabling the syntax is deprecated."` Unfortunately if you switch to using the new rspec `allow()` syntax you run into a different problem because FactoryGirl does not include RSpec's methods: `NoMethodError: undefined method 'allow' for #`. Does anyone know a solution to this which works with the latest versions? – Jazz Dec 05 '14 at 19:46
  • 3
    You can do `def cimg.validate_img; true; end` and effectively stub it out without rspec. – Mladen Jablanović Jun 25 '15 at 11:53
  • @Jazz There's a way to use the allow syntax. See my answer. – Ho-Sheng Hsiao Jun 30 '15 at 04:18
5

A factory should produce "real world" objects therefore it's a bad practice (and error prone) to change behaviour (i.e. stub) in a factory.

You can do

let(:user) instance_double(User, FactoryGirl.attributes_for(:user))

before do
  allow(user).to receive(:something).and_return('something')
end

and if your before clause gets too big you may want to extract it to a separate method or create a mock child class that overrides methods you want to stub.

thisismydesign
  • 21,553
  • 9
  • 123
  • 126
3

You might also consider using FactoryGirl#build_stubbed.

Su Zhang
  • 2,155
  • 2
  • 14
  • 11