I'm currently working on a project and we started migrating our tests to isolated test (no rails dependency, and using stubs and mocks).
The thing is that until all the current tests are being isolated, we have to run the tests together with the isolated tests, which will start the rails environment.
The problem comes when, in the isolated tests, there is a fake class (class Foo; end;), it will override the original class for the rest of the tests.
Example:
In the foo_spec.rb we have this line
class Bar; end;
This would override the Bar class for the next nonisolated tests, and would cause a lot of fails.
There are 2 approaches I could figure in order to get rid of this:
- either comment out the fake classes when the tests are run with rails env
- put the isolated tests in another folder and run them separated from the rest (this would make more sense)
Can you think of a nicer way to deal with this?
we are using rspec (which should not change anything) and have our rails spec located in spec with their own spec_helper.rb file that is loading the env and all the ugly stuff.
in spec_fast folder we have all the spec that can run without rails, with their own spec-helper that only loads our independent lib folder.
for our ci-server we let both spec folder run in a different task:
if Rails.env.test?
require 'rspec/core/rake_task'
require 'ci/reporter/rake/rspec'
RSpec::Core::RakeTask.new(:all_fast) do |t|
t.pattern = 'spec_fast/**/*_spec.rb'
end
RSpec::Core::RakeTask.new(:all_slow) do |t|
t.pattern = 'spec/**/*_spec.rb'
end
task :all => ["ci:setup:rspec", :all_fast, :all_slow]
end
it should also be possible to just put them into separate subfolders like spec/rails and spec/fast but i did not try it out because it means to do a lot of path-changing in spec-files.
I don't know if it's right, but I end up not actually assigning such contextual dummy manually created dummy classes to constants.
Instead of:
#no
class Foo
#something
end
Instead:
foo = Class.new do
#stuff
end
And you can foo.new or foo.class_method to your heart's content. Could be in #foo too. But you aren't assigning it to the constant Foo like ordinary class definition does, you're creating an 'anonymous' class and assigning it to an ordinary variable, scoped to just within the area you need it.
Note: I'm not saying this is "right" way to do things with rspec, I never feel like I know the right thing to do, the right thing to do might be to somehow not create classes like this at all, or use some weird factorygirl thing which I don't understand or something. But when I need to create 'dummy' type classes just to the scope of a particular test or block, that's what I do.
Related
I have been looking around the internet for a long, frustrating, while, and I'm still quite confused as to what the purpose of the teardown() method is in MiniTest and how it should be used.
The basic gist I have is that it is 1-run after each test, and 2-undoes things that were done during the test in order to allow future tests to run in a clean environment.
However, I am unclear on the nature of things it needs to undo: Does it need to empty the DB? Reset class variables? etc.
I am also unclear on whether the method is supposed to be explicitly filled out or not. I have found many examples where teardown() is completely left out of the example.
(My best guess is that there is a super-method teardown that runs automatically and takes care of certain things. That would explain why it is often left out, and would also explain why some things are reset in a given teardown() method and some aren't. But I still don't know which things are and which aren't.)
In short:
Does teardown need to be explicitly created? In what circumstances would it need to be overwritten and in which wouldn't it be?
The simplest answer is that you use #teardown in every test but you don't need to worry about it. Similar to the Rails lifecycle, there is a Minitest lifecycle. There are hooks to inject logic and behavior to be used by your tests. The main one in Rails tests is the database transaction. Each test that uses ActiveSupport::TestCase runs in a database transaction. The flow is like this:
Create the database transaction (Minitest::Test#setup)
Run your test method (MyTest#test_something)
Rollback the database transaction (Minitest::Test#teardown)
It is somewhat common for folks to use #setup to create objects for use in tests. After the test method completes the test object is garbage collected, so most folks don't use #teardown to clean up after the test. Because of this #teardown is typically a more advanced feature that you don't normally use when writing tests. I see it used much more often in testing libraries that enhance Minitest.
But there are times I do use #teardown in my tests. Here is an example of when I might use it.
require "minitest/autorun"
class Foo
def initialize namer
#namer = namer
end
def name
#namer.name
end
end
class FooTest < Minitest::Test
def setup
#namer_mock = Minitest::Mock.new
#namer_mock.expect :name, "foo"
#foo = Foo.new #namer_mock
end
def test_name
assert_equal "foo", #foo.name
end
def teardown
#namer_mock.verify
end
end
I have a helper class, ApplicationHelper, that has a method, build_links(). I have another class, AppleClass, that refers to that method.
AppleClass
def foo
....
build_links
end
end
ApplicationHelperClass
def build_links
main_app.blah_path(1)
end
end
The complication here is that there's an Engine, so I usually explicitly reference "main_app.blah_path" not just "blah_path".
The test against foo passes by itself, in its file, and when I run all helpers. It fails, though, when I include it in all the unit tests - "rake spec:suite:unit", and with our entire suite. All Apple tests pass, all ApplicationHelper tests pass. The only failing ones are when one method is referring to the other method, in routes, outside of the engine, in the full suite.
`undefined local variable or method `main_app' for #
<RSpec::Core::ExampleGroup::Nested_45::Nested_1:0x007fc134b30130>`
My suspicion is that the test helper, or some config, is not loading the engine's routes early enough, and thus links to "main_app" don't make sense. If I remove main_app, the test fails until it's run in the main suite.
Does anyone have tips on troubleshooting what's really going on? Also, could I kickstart the routing somehow in test_helper?
ruby-1.9.3-p385, rails 3.2.13, rspec 2.13.0
I had the same issue and found that if I added this method to the top of my Controller RSpec test case, then it resolved the issue entirely.
def main_app
Rails.application.class.routes.url_helpers
end
I think this issue is related to this question
Declaring a class for (fast) testing purposes is great:
require 'fast_helper'
require 'site_search'
class Post; end # This allows not to load the whole Rails env
describe SiteSearch do
it "searches on posts" do
Post.stub_chain(:scoped, :by_term).with("ruby").and_return ["post1", "post2"]
SiteSearch.by_term("ruby").should == ["post1", "post2"]
end
end
The problem with it is that it seems to break autoloading of rails models when the whole suite of specs is run.
The models aren't loaded anymore when the class gets declared before.
There are 4 ways of injecting the unloaded dependencies:
Declare the class (as in example here)
Set/remove const
Stub a wrapper method
Actually load them
I only want to use the 1st one.
The question: keeping the same specs structure, how can I tell rails to actually load the models even though the class is already declared?
In order for your empty class preemption trick to work, you must have set your app config.cache_classes = false, hence eager loading is not happening unless you call
Rails.application.eager_load!
When running the whole test suite you need to make sure classes preload, then empty redefinition should have no effect.
Now the question is how you control that it is run only if the full test suite is called. The honest answer is I don't know, but you can surely control it from the environment. Somewhere in your rspec helpers you initialize rails, update it to something like:
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
Rails.application.eager_load! unless ENV["FORCE_EAGER_LOAD"].blank?
and then call rspec on full suite as:
FORCE_EAGER_LOAD=t rspec
Some of my Rspec tests have gotten really really big (2000-5000 lines). I am just wondering if anyone has ever tried breaking these tests down into multiple files that meet the following conditions:
There is a systematic way of naming and placing your test (e.g. methods A-L gos to user_spec1.rb).
You can run a single file that will actually run the other tests inside other files.
You can still run a specific context within a file
and, good to have, RubyMine can run a specific test (and all tests) just fine.
For now, I have been successful in doing
#user_spec.rb
require 'spec_helper'
require File.expand_path("../user_spec1.rb", __FILE__)
include UserSpec
#user_spec1.rb
module UserSpec do
describe User do
..
end
end
If your specs are getting too big, it's likely that your model is too big as well -- since you used "UserSpec" here, you could say your user class is a "God class". That is, it does too much.
So, I would break this up into much smaller classes, each of which have one single responsibility. Then, test these classes in isolation.
What you may find is that your User class knows how to execute most logic in your system -- this is an easy trap to fall into, but can be avoided if you put your logic in a class that takes a user as an argument... Also if you steadfastly follow the law of demeter (where your user class could only touch 1 level below it, but not two).
Further Reading: http://blog.rubybestpractices.com/posts/gregory/055-issue-23-solid-design.html
I've got a document management system which I programmed in Rails 2.3.8 a while ago and I've been retrofitting some rspec tests to the project before refactoring and making enhancements.
The problem is a lot of my tests require stubbing out most of the File & FileUtils libraries as there is a lot of file interaction within models. Is there a better way to test File & Directory actions without having to touch the filesystem at all?
For instance I stub out mkdir_p:
FileUtils.stub!(:mkdir_p)
And when I'm moving I use something like this:
FileUtils.should_receive(:mv).with("from path","to path")
Use fakefs. It's perfect for the purpose.
I would not recommend to alter File or FileUtils classes, because these changes will affect all your running unit tests. It will be hard to track these changes when you have a lot of different tests and your tests will start affecting each other (which is generally bad testing practice).
I propose to explicitly use a simple manual Dependency Injection approach like constructor injection:
class UnderTest
def initialize file_creator = FileUtils
#file_creator = file_creator
end
def use_file_utils
#file_creator.mkdir('some_dir')
end
end
In RSpec test:
fake_file_utils = mock "FileUtils"
test = UnderTest.new fake_file_utils
test.use_file_utils