RSpec naming conventions for files and directory structure - ruby-on-rails

I'm writing RSpec tests and I have come to a point where I am not reading the same opinions on different websites. The directory structure for RSpec is clear when we are dealing with spec/controllers and spec/models directories, but the conflicting information I am getting deals with testing views.
I would like to just put these tests in a spec/integration/ directory, but I have read that it's supposed to be under spec/integration, but another book says spec/requests. Does this matter?
To further complicate the situation, I have read conflicting information on naming the actual file names of the tests! For example, if I had a controller/model/view directory called 'people' (I i use haml), I should name the files like this:
spec/integration/people.html.haml_spec.rb
However, another book suggests this:
spec/requests/people_spec.rb
I would like a little explanation of naming conventions within RSpec and WHY I should name the view tests specific names, as well as whether or not it matters to put them under spec/integration or spec/requests. I feel like I'm going with the wind here.
Any help is greatly appreciated.

Check the dates of the books and the versions of RSpec for which they were written. The naming structure has changed slightly over time.
According to the docs for rspec-rails, request specs can go in spec/requests, spec/api, or spec/integration. I prefer to put request specs in spec/requests.
To make things more interesting, if you are using Capybara with rspec-rails, it will work with spec/requests for Capybara 1.x, and spec/features for Capybara 2.
As to individual spec file names, when there is a specific class under test, like a Rails model, you should use an analogous spec file name:
app/models/user.rb -> spec/models/user_spec.rb
View specs should use the template name:
app/views/users/index.html.erb -> spec/views/users/index.html.erb_spec.rb
Namespaced models should include the namespace in the spec file path:
app/models/admin/user.rb -> spec/models/admin/user_spec.rb
The RSpec scaffold generator is a good guide for showing where these specs belong.
When there is no specific class under test, as is the case with request specs, IMHO you should feel free to use a name that describes the thing being tested. E.g. spec/requests/place_an_order_spec.rb.

The files read by the rspec gem are simply those that end in _spec.rb and that are anywhere in the hierarchy below the spec folder as long as the top of the hierarchy is an alpha-numeric word (that is, files under a folder named spec/##/ would not be considered in generating specs.) This is the relevant line of code that implements this, in /gems/rspec-rails-2.14.1/lib/rspec/rails/tasks/rspec.rake:
namespace :spec do
types = begin
dirs = Dir['./spec/**/*_spec.rb'].
map { |f| g=f.sub(/^\.\/(spec\/\w+)\/.*/, '\\1') ; puts ">>> Found #{g}."; g }.
uniq.
select { |f| File.directory?(f) }
Hash[dirs.map { |d| [d.split('/').last, d] }]
end
So all the text in the filename previous to _spec.rb is a convention - it doesn't change how Rails processes the files.
I'd have to read through the code a lot more to figure out what the significance of each folder name, under spec, is - it gets passed through to the Rake spec task that's created, so it's being used for something but I don't know what.

Related

Rspec generator to create a *_spec.rb file to test a PORO

This website lists the various generators available to create spec files for specific rails files.
I know how to generator a spec file for a model.
Ex: rails generate rspec:model Customer creates this file:
#spec/models/customer_spec.rb
require 'rails_helper'
RSpec.describe Customer, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
I created a service: MYPORO. It is a PORO (plain old ruby object), located at app/services/my_poro.rb.
How do I generate a spec file in order to create tests for the MyPoro class and its instances? The biggest issue I have is I don't know what type to provide, because it is not a :model. I would just manually create the file, but again: I don't know what :type to provide inside the file.
No "Type" Required for POROs
The biggest issue I have is I don't know what type to provide, because it is not a :model. I would just manually create the file, but again: I don't know what :type to provide inside the file.
You don't need to specify a type for plain Ruby objects. The :type tells the RSpec Rails extensions loaded by the rails_helper module (installed by rspec-rails) how to run certain types of tests for models, routes, and so forth.
I generally still load rails_helper instead of spec_helper for these types of specs, but in most cases you should be able to simplify to something like:
require 'spec_helper'
describe Customer do
pending "insert tests on your non-Rails Customer class"
end
touch spec/services/my_poro_spec.rb ?
You don't need generators for everything. ;)

Ruby, Rspec, and requiring test contoller

I'm new to Ruby (RoR) and rspec.
I have the basic project scaffold and a controller within project/app/controllers/books_controller.rb
Then I'm trying to setup rspec in project/spec/controllers/book_spec.rb
There also exists project/spec/spec_helper.rb that book_spec.rb requires. This file is where I'm trying to include the books_controller.rb file.
How do I include the books_controller.rb file so book_spec.rb has the Book object so when I run rspec, it works?
Currently I've tried things like require_relative "../app/controllers/books_controller" and it doesn't work. It gives me an error like:
uninitialized constant ApplicationController ... in 'require_relative'
Any help would be great.
You don't need to manually include your controllers, just follow the rspec naming conventions and describe the desired object and everything will work fine.
Your controller spec should be named books_controller_spec.rb. That is the exact same name as your controller with a _spec added at the end. This is not a hard requirement but a highly suggested convention as a book_spec.rb really implies a Book model spec rather than a controller.
The critical part is that in your spec you describe the desired object, e.g.
describe BooksController do
#...
end
You might also want to have a look at the rspec-rails gem which provides some useful generators for specs. Ideally you integrate with Rails so whenever you generate a new model or controller a new spec is created for you as well.

rspec for helper class expects module even though there is no module

I added a helper class to my rails project, Foo, at app/helpers/foo.rb. It looks like this.
class Foo
#....stuff
end
I use it in some models, and everything works fine. However, in spec/helpers/foo_helper_spec.rb, I have
require 'spec_helper'
describe Foo do
end
This causes rspec to crash (not report any failed tests, but actually crash), saying
/actionpack-3.2.12/lib/abstract_controller/helpers.rb:153:in `include': wrong argument type Class (expected Module) (TypeError)
If i remove the describe line and just have the file empty, everything works, but I'd like to add some tests soon.
Anyone know how I can fix this?
Thanks.
rails follows the paradigm of convention over configuration. one of those conventions is, that you put modules into the helpers folder, because they get included into your controllers and views.
that's why rspec fails when it tries to include your module, which is actually a class.
if you need to have a real class in there, i think you should put it in a different directory to make it obvious, that this is not a usual rails helper!
for example, if you are implementing some kind of decorator for your views, put it in a decorators folder. if you are implementing some kind of adapter for your model, put it in the models folder or some model subfolder.

Can I run a Rails engine's specs from a real app which mounts it?

I have a Rails Engine meant to provide some models and controllers to a larger project of ours. There's a pretty decent set of specs for the Engine, using a bunch of mocks and some full-scale models and controllers within the engine's dummy app to make sure the Engine is doing what it's supposed to and working with the larger application.
However, even with all tests passing, I frequently find broken behavior when I update the engine in the larger application. If my tests are passing but the behavior is broken, clearly something's wrong with the tests, but what? Am I mocking too much, or not enough?
To get me closer to figuring this out, I'd like to be able to run the engine's tests from inside the full application. This seems like it should be possible, but I don't understand rspec thoroughly enough to get a handle on how.
(This is related to this question but not quite the same; I'm not trying to run all the specs from one command, just to run the engine's specs within the full app environment. This also seems to be related. Actually, I've read every question tagged with both rspec and rails-engines--there aren't many--and they're all either not what I need, or unanswered.)
The simplest solution would be to specify the paths in rspec command. If you have directory structure
/project
/engine
/engine_2
Then you do and should run all the specs
cd project
rspec spec/ ../engine/spec ../engine_2/spec
But if you want to run specs on Continous Integration or just this doesn't seem to be comfortable I solved this problem with a customized rake spec task, changing the pattern method.
lib/task/rspec.rake should look like this
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
RSpec::Core::RakeTask.module_eval do
def pattern
extras = []
Rails.application.config.rspec_paths.each do |dir|
if File.directory?( dir )
extras << File.join( dir, 'spec', '**', '*_spec.rb' ).to_s
end
end
[#pattern] | extras
end
end
In engine class you add a path to config.rspec_paths
class Engine < ::Rails::Engine
# Register path to rspec
config.rspec_paths << self.root
end
And don't forget to initialize config.rspec_paths somewhere in a base project.
If you want to add factories then you can create initializer, you can find solution somewhere here on stackoverflow.
Not sure if this solution is the best but works for me and I am happy with that. Good luck!

How do I get autotest to properly inflect singular models and plural controllers

Just as an example, if I have a Book model and a BooksController, autotest, part of the ZenTest suite will pick up the association between the two and load test/unit/book_test.rb and test/functional/books_controller_test.rb into the test suite. On the other hand, if I have a Story model and a StoriesController, autotest refuse to "notice" the test/functional/stories_controller_test.rb
Unfortunately ZenTest isn't a rails plugin, so it doesn't benefit from ActiveSupport's pluralize method. Therefore it uses simple regular expressions to match the filenames. Have a look at ZenTest/autotest/rails.rb to see a list of the existing mappings for Rails.
At the end you have two options:
(monkey) patch your own mapping rule
forget about pluralization
Hope this helps!
You can override the mappings in your .autotest file. Either in your home directory or at the root of the project. You could require 'active_support' there to get String#pluralize and String#singularize.
Borrow the code from the rspec-rails plugin in lib/autotest/rails_rspec.rb, it already seems to do the singular/plural magic with ActiveSupport. You'll probably need to yank out the RSpec specific assumptions from there, though.
I finally figured out what was going on, and it had nothing to do with pluralization after all.
It had everything to do with the word "stories" which can be a special directory for one of the testing libraries (RSpec? Cucumber? I forget) So it was listed in my ~/.autotest config file as an exception! I'm not sure when I cut and pasted the snippet into the file, probably when I was first getting starting with ZenTest and didn't know what I was really doing.
Autotest.add_hook :initialize do |at|
%w{... stories ...}.each {|exception|at.add_exception(exception)}
end
Adding a trailing slash ("stories/") restored the test and removed the brick marks from my forehead.
So I guess the lesson learned is: check for stray configuration files when debugging.

Resources