How to stub out a entire class - ruby-on-rails

I'm starting the process of refactoring my tests to run without loading Rails. Most of my tests are still standard rspec tests that load rails. My problem is that in the test file below, I override one class that is used in many other test files. So even when these tests pass, the others fails. I'm looking to stub that Project class after getting a lot of advice here. (I would rather stub the project class in this file than to reload the project class in all the other test files that need it.)
Here's what I have now.
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
class Project
attr_accessor :general_media
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
include ActiveModel::Validations
include ActiveRecord::Callbacks
end
require_relative '../../../app/models/project/media.rb'
describe 'Project::Media' do
#then all the tests
I tried to to create a FakeProject class and to have Project::Media inherit from that by stubbing Project with FakeProject, but it didn't work. Here's what I've got.
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
class FakeProject
attr_accessor :general_media
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
include ActiveModel::Validations
include ActiveRecord::Callbacks
end
class Project; end
require_relative '../../../app/models/project/media.rb'
describe 'Project::Media' do
before(:all) { stub_constant!("Project", FakeProject) }
First off, I'm still overriding the project class and secondly, the stubbig isn't working. I still get an error about validations not working.
undefined method `validate' for Project::Media:Class (NoMethodError)
Any suggestions? My goal is to stub the Project class only for the tests in this media_spec.rb test file rather than override how the Project class behaves for all tests.

You can create a Fake class using the rspec syntax. Something like:
let(:fake_class) { Class.new }
stub_const("Project", fake_class)
And if you want to stub any method for that Project class, you can do like :
dummy_class = Project # the same name that you used above
# stub any method and return anything that you want to return
dummy_class.stub_chain(:any_method).and_return(anything)

Related

Test (rspec) a module inside a class (Ruby, Rails)

I have this to work with...
class LegoFactory # file_num_one.rb
include Butter # this is where the module is included that I want to test.
include SomthingElse
include Jelly
def initialize(for_nothing)
#something = for_nothing
end
end
class LegoFactory # file_num_2.rb
module Butter
def find_me
# test me!
end
end
end
So, when LegoFactory.new("hello") we get the find_me method as an instance method of the instantiated LegoFactory.
However, there are quite a few modules includes in the class and I just want to separate the Butter module without instantiating the LegoFactory class.
I want to test ONLY the Butter module inside of the LegoFactory. Names are made up for this example.
Can this be done?
Note: I cannot restructure the code base, I have to work with what I have. I want to just test the individual module without the complexity of the rest of the LegoFactory class and its other included modules.
A way to do it is to create a fake class that includes your module in order to test it:
describe LegoFactory::Butter do
let(:fake_lego_factory) do
Class.new do
include LegoFactory::Butter
end
end
subject { fake_lego_factory.new }
describe '#find_me' do
it 'finds me' do
expect(subject.find_me).to eq :me
end
end
end
You can also implement in the fake class a mocked version of any method required by find_me.

require JUST ActiveModel::Validations in an rspec test

I have a class that I'm trying to test that includes ActiveModel::Validations
module SomeModule
class SomeClass
include ActiveModel::Validations
end
end
I'm trying to test it without spec_helper to keep it fast, but a simple require 'activemodel' at the top of the spec doesn't work. I keep getting an uninitialized constant SomeModule::SomeClass::ActiveModel(NameError). for the spec file:
require 'activemodel'
describe SomeModule::SomeClass do
end
Any tips on solving this? Thanks in advance!
You will need to include active_model in your module/class file.
# /some_class.rb
require 'active_model'
module SomeModule
class SomeClass
include ActiveModel::Validations
end
end
Spec,
# /some_class_spec.rb
require './some_class'
describe SomeModule::SomeClass do
end
You'll want to change the paths to match your files. I doubt this will speed up your specs when run with other specs that include the whole Rails stack, but when run by itself this will be a little faster.

Include module in all MiniTest tests like in RSpec

In RSpec I could create helper modules in /spec/support/...
module MyHelpers
def help1
puts "hi"
end
end
and include it in every spec like this:
RSpec.configure do |config|
config.include(MyHelpers)
end
and use it in my tests like this:
describe User do
it "does something" do
help1
end
end
How can I include a module into all MiniTest tests without repeating myself in every test?
From the Minitest README:
=== How to share code across test classes?
Use a module. That's exactly what they're for:
module UsefulStuff
def useful_method
# ...
end
end
describe Blah do
include UsefulStuff
def test_whatever
# useful_method available here
end
end
Just define the module in a file and use require to pull it in. For example, if 'UsefulStuff' is defined in test/support/useful_stuff.rb, you might have require 'support/useful_stuff' in either your individual test file.
UPDATE:
To clarify, in your existing test/test_helper.rb file or in a new test/test_helper.rb file you create, include the following:
Dir[Rails.root.join("test/support/**/*.rb")].each { |f| require f }
which will require all files in the test/support subdirectory.
Then, in each of your individual test files just add
require 'test_helper'
This is exactly analogous to RSpec, where you have a require 'spec_helper' line at the top of each spec file.
minitest does not provide a way to include or extend a module into every test class in the same way RSpec does.
Your best bet is going to be to re-open the test case class (differs, depending on the minitest version you're using) and include whatever modules you want there. You probably want to do this in either your test_helper or in a dedicated file that lets everyone else know you're monkey-patching minitest. Here are some examples:
For minitest ~> 4 (what you get with the Ruby Standard Library)
module MiniTest
class Unit
class TestCase
include MyHelpers
end
end
end
For minitest 5+
module Minitest
class Test
include MyHelperz
end
end
You can then use the included methods in your test:
class MyTest < Minitest::Test # or MiniTest::Unit::TestCase
def test_something
help1
# ...snip...
end
end
Hope this answers your question!
One thing I will do is create my own Test class inheriting from Minitest::Test. This allows me to do any sort of configuration on my base test class and keeping it isolated to my own project1.
# test_helper.rb
include 'helpers/my_useful_module'
module MyGem
class Test < Minitest::Test
include MyUsefulModule
end
end
# my_test.rb
include 'test_helper'
module MyGem
MyTest < Test
end
end
1 This is most likely unneeded, but I like keeping all my gem code isolated.

Testing without loading Rails: faked class need so be 'unfaked' after tests run

I've refactored part of my test suite to not load rails when performing tests. The code below is an example of a test file that loads only selection pieces of Rails. It also fakes the "project" class. My problem is that this faked project class ends up overriding the normal project class and all other tests that involve the project class now fail.
How do I un-override my project class after this test file runs?
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string'
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
require_relative '../../../app/models/project/media.rb'
UPDATE: I think this comes close to what I need, except that I'm getting an error about Project being an uninitialized constant. I must be instantiating this test class incorrectly.
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
require_relative '../../../app/models/project/media.rb'
describe Project::Media do
before(:all) do
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
end
after(:all) { Object.send(:remove_const, :Project) }
#then all the tests
You should be able to un-define a class with Module#remove_const.
Object.send(:remove_const, :Project)
Its a private method so you'll need to use send rather than a regular method call.
UPDATE:
Perhaps try the following:
require 'active_model'
require 'active_model/validations'
require 'active_record/callbacks'
require 'active_support/core_ext/string' #used for '.blank?' method
class Project
include ActiveModel::Validations
include ActiveRecord::Callbacks
def initialize(attributes = {})
#general_media = attributes[:general_media]
end
attr_accessor :general_media
end
require_relative '../../../app/models/project/media.rb'
describe Project::Media do
after(:all) { Object.send(:remove_const, :Project) }
...
You will need to declare the Project class before the describe block if the subject depends on it. Also assuming your Media model depends on it before you require that.
Later tests for the Project class will need to reload it, assuming they are separate tests you can just require your project model class in that test file if you want a minimal (fast) test, or via the normal spec_helper if you want to load the whole Rails application (slow).
As discussed in comments it might be easier to simply stub out the Project class rather than redefine it.

how to write/run specs for files other than model/view/controller

Using rails and rspec it's easy to have rspec generate the necessary files for me when I'm using the rails generate command with models/views/controllers. But now I want to write specs for a module I wrote. The module is in /lib/my_module.rb so I created a spec in /spec/lib/my_module_spec.rb
The problem I'm having is that when I try to do rspec spec/ the file my_module_spec.rb is run but the reference to my module in lib/my_module.rb can't be found. What's the right way to do this?
Just FYI the my_module_spec.rb file does have require 'spec_helper' in it already
require 'spec_helper'
describe "my_module" do
it "first test"
result = MyModule.some_method # fails here because it can't find MyModule
end
end
You could try including the module and maybe wrapping it in an object
require 'spec_helper'
#EDIT according to
# http://stackoverflow.com/users/483040/jaydel
require "#{Rails.root}/lib/my_module.rb"
describe MyModule do
let(:wrapper){
class MyModuleWrapper
include MyModule
end
MyModuleWrapper.new
}
it "#some_method" do
wrapper.some_method.should == "something"
end
end
Does your module contain class methods or instance methods? Remember that only class methods will be available via
MyModule.some_method
meaning that some_method is defined as
def self.some_method
...
end
If your module contains instance methods, then use Jasper's solution above. Hope that clarifies.
Put the following in your config/application.rb file:
config.autoload_paths += %W(#{Rails.root}/lib)
I was just wrestling with the same problem, and the above worked for me. There's not really any reason you should have to jump through hoops to be able to access your lib/ files from RSpec and write tests for them.

Resources