I have a large model that takes time to initialize in my RSpec tests.
I want it potentially available to every example, but want to only load it if an example requires it.
This seems like the perfect use for let()'s lazy loading - only load it when you need it.
In any particular spec file I can do
require "spec_helper"
feature "foo" do
let(:big_class) { MyBigClass.new(bar) }
...
end
This will make big_class available to every example in that spec file.
Is there a way to make this more global so that EVERY spec file and example can use it? I couldn't find a good way to initialize let inside the spec helper.
You may simply define a shared context and include it in every example. Regarding your particular question, it should look like following:
RSpec.shared_context "Global helpers" do
let(:big_class) { MyBigClass.new(bar) }
end
RSpec.configure do |config|
config.include_context "Global helpers"
end
However, it's rarely a good idea to include a shared context in all examples, and that big_class helper from your question really looks like something domain-specific. You can steer the shared context inclusion by metadata, for example when you want to include given shared context in feature specs only (they all have :type => :feature metadata set by default), you can do it this way:
RSpec.configure do |config|
config.include_context "Feature spec helpers", :type => :feature
end
You might consider other approaches:
Use mock objects instead of real ones.
Refactor the initializer and extract the slow operation to another method
Mock objects of course bring their own set of drawbacks; they can become stale and make tests more brittle. But for some tests that is not an issue.
Refactoring initializers is a favorite of mine. E.g.
MyBigObject.new(args)
becomes
MyBigObject.new(args).setup
or :load_data or :connect_to_database_on_the_moon or whatever is taking a long time. You get the picture.
Obviously this means changing your code, but I find that often works out to be helpful in other ways, and it certainly makes testing easier.
You don't want to use let. From the docs:
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
You'll end up instantiating MyBigClass lots of times. I would recommend creating a global helper method somewhere in spec_helper.rb (or similar) that used memo-ization on it's own to return the cached value if it's already been setup.
Also be very careful with all this as you're violating the rule of isolated tests. Might be fine for what you're doing, but it's a red flag.
Use global before hook with an instance variable
From the docs:
RSpec.configure do |c|
c.before(:example) { #big_class = MyBigClass.new(bar) }
end
RSpec.describe MyExample do
it { expect(#big_class).to eql(MyBigClass.new(bar)) } # This code will pass
end
For more details check the suggestions in this answer
Related
I did not find the correct place to use the require method in any documentation. I understand that it is desirable to use the require method at the very beginning of the script, but is it possible and how correct is it to use the require method in the initializer class constructor?
Usually i make like this
require 'my_file'
how good is it to do the option below
class MyClass
def initialize
require 'my_file'
end
end
Read require method documentation
I did not find the correct place to use the require method in any documentation.
The "correct" place depends on your requirements and also on your coding style.
is it possible and how correct is it to use the require method in the initializer class constructor?
It is possible. Whether it is "correct" or not depends on your definition of "correct" and it depends on what you want to do.
require only does three things:
Search for a file in the load path.
Check whether the file was already run.
If it wasn't, run the file.
That's all require does.
If you require a file in the initializer, require will only run when you call the initializer (which is usually when you instantiate a new object).
If you need some functionality from the file before you instantiate an object, you are out of luck, since the require will not have run.
Also, you will run require every time you instantiate an object, but since require only runs the file once, that will simply be a waste of time, since it will still search for the file and check whether it was already run every time.
Lexically nesting a call to require like this can also be confusing. Someone who is not familiar with Ruby may assume that by requireing myfile inside the initialize method, the definitions inside my_file might be scoped purely to the initialize method, but that is obviously not the case: require simply runs the file, it does not perform any scoping. The definitions inside my_file will be defined wherever my_file says they are.
In the same vein, someone who is reviewing your code might assume that you think that this creates some sort of scoping and might now wonder whether your code has some sort of bug or (worse) security leak based on that assumption.
And note that "someone who is reviewing your code" might very well be yourself in two years, when you have no idea anymore why you wrote the code this way.
You can put it on top of the file before the class declaration. So you have:
require 'my_file'
class MyClass
def initialize
end
end
It's possible, it will work correctly, but it's highly unusual. I guarantee this will raise questions in code review.
Normally, requires are placed on top of the file.
Declaring the require "./file_name" is not recommended to use in the constructor method as it is often used to create the instance variable and particularly: if you are going to use an instance or global variable from that required file class as a parameter to the initialize (constructor) method of the current class, it may end up with an error.
You may use these ways to require the file at the top of the class:
require 'my_file'
class MyClass
def initialize
puts "this is a constructor"
end
end
or you may also require it just before calling your class is highly recommended to use require
class MyClass
def initialize
puts "this is a constructor"
end
end
require 'my_file'
object = MyClass.new
object.some_method
This allows you to use all the instance, class, method, object...etc.. in your current class.
I have an initializer to load some data from a csv file to a global variable to be used in a model. Right now it looks like this:
...
XYZ = Hash[*CSV.open...]
Now I want to write an rspec spec for this initializer. I am using rubocop-rspec and it says that it is that describe should take not a string, but a class or a module (https://www.rubydoc.info/gems/rubocop-rspec/1.1.0/RuboCop/Cop/RSpec/DescribeClass).
Assuming this is reasonable, what are the options to convert initializer to a class?
There is an option to use describe XYZ but it does not seem right.
1) Well, you could wrap Hash[*CSV.open...] into some helper class or module, say, DataLoader, cover it with tests and then invoke like XYZ = DataLoader.do_stuff(filename) in your initializer.
But there is one "issue" with this approach: the class will be really "dumb" (adding no custom logic at all, just wrapping a couple of methods from stdlib) and will be used in an initializer only - so will be called just once. I doubt all this additional boilerplate worth it.
So, I'd probably consider something simpler:
2) Just ignore (disable) this particular cop for this particular case. Conventions are good and rubocop is just great, but sometimes breaking the rules leads to a cleaner code than blindly following them. For example, I sometimes add tests for destructive rake tasks - in this case, I have to break this convention too, and I do it when necessary because it is still better than artificial workarounds for the sake of conventions... Need to test a minor piece of initializer's logic? Just do it :)
A new project with the RSpec 3.2 and Rails 4.2.
As a good citizen I'm trying to do the "right" thing with the new RSpec (disable monkey patching, do not infer the type of examples etc).
The problem I have is that I want to test this little class:
# app/models/session.rb
class Session # NOTE: This is a simple transient class, almost plain Ruby class
include ActiveModel::Model
attr_accessor :email, :password
end
with something like:
# spec/models/session_spec.rb
require 'spec_helper'
RSpec.describe Session, type: :model do
it { should validate_presence_of :email }
it { should validate_presence_of :password }
end
Obviously, when I run the spec I'm getting the uninitialized constant Session (NameError) which makes sense as the 'spec_helper' does not do anything to load that particular class.
I could have user the rails_helper instead but that is simply too much for that little class since it has nothing to do with rails at all.
Willing to do "the right" I'm puzzled with the following questions
Does the Session spec even need to be marked with type: :model?
Should I modify the spec_helper to change the $LOAD_PATH?
If not, is there a way to autoload session.rb file but without loading up the whole Rails?
Should the transient Session class be under app/models? I treat it as a model, it's just not persistent.
The easiest thing to do is of course to just use rails_helper, but for what I need to do that is unnecessary and I'd love to keep the spec file fast.
The point of distinguishing between rails_helper and spec_helper is that you want just a minimal setup with spec_helper, so I would recommend against modifying $LOAD_PATH there. The only advantage you get is that specs run quicker if you run only specs that use only spec_helper, for example when you run just the single spec file with rspec spec/models/session_spec.rb. If you run the entire suite and require rails_helper in any spec, it will generally not run any faster.
Basically, you have two options:
If you don't bother about the single spec file running a bit slower than it could, and you don't want to require the Session model manually, I would say it's perfectly fine to use rails_helper for the sake of simplicity.
If you actually do care about the single spec file running a bit faster, you can require_relative '../app/models/session' manually at the top of the spec file. Autoloading is Rails magic, and you don't want Rails for speed reasons; therefore, you need to do it manually.
You don't need to mark the spec as type: :model unless you have specific spec helpers that are only included for type: :model and you intend to use them. However, it makes sense to mark them that way when you treat it like a regular model and use rails_helper.
I'm sauntering through Michael Hartl's Rails Tutorial right now, and am finding that I'm constantly encouraged to use wonderful methods that inexplicably do amazing things. He does a generally competent job of explaining what they do, but there is no real nitty gritty of why and how they work.
Specifically, I have just been plundering the rspec gem on github searching for the source code to the "describe" method. I cannot find it. Having now read a large amount of the source code (at an apprehension rate of about 25%) searching for it, I know that once found, I will need to look at its parent classes and modules to understand a certain amount of inheritance before I can really grasp (and then never let go of) the flesh and bones of "describe".
I don't mind struggling to grasp the concept, I'm a fan of attempting to read code in new languages before I fully understand it so that I can read it again later and use the comparison of my comprehension as a gauge of my fluency. I'd just like a kicker. Either a description or a file location with maybe a little helper hint to get me started.
For example...
I found this:
# RSpec.describe "something" do # << This describe method is defined in
# # << RSpec::Core::DSL, included in the
# # << global namespace (optional)
and rpsec/core/dsl states:
# DSL defines methods to group examples, most notably `describe`,
# and exposes them as class methods of {RSpec}. They can also be
# exposed globally (on `main` and instances of `Module`) through
# the {Configuration} option `expose_dsl_globally`.
but then there is no "class Describe" or def "describe" or such in that file.
SO: can anyone tell me where the "describe" method is, how it works, exactly, or (if not) why I am naively searching for the wrong thing in the wrong locations?
As you may know, there is no difference between describe and context methods and you can use them interchangably. Rspec developers could not let themselves to repeat the same code for different method names, so they moved the declaration to
module RSpec
module Core
class ExampleGroup
def self.define_example_group_method(name, metadata={})
# here they really define a method with given name using ruby metaprogramming
define_singleton_method(name) do |*args, &example_group_block|
And call that method a bit later for all the same-functionality DSL methods:
define_example_group_method :example_group
define_example_group_method :describe
define_example_group_method :context
So in case you are looking for describe method source, dive into define_example_group_method with assumption that name argument equals to describe and example_group_block is your block body.
The RSpec code base is not a trivial thing to get your head round. However, these links should get you started ...
This line defines the describe keyword:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/example_group.rb#L246
The method above that line does the heavy lifting for you. Take your time reading it.
This part then exposes the generated method:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/dsl.rb#L54
Good luck!
I was wondering whether it is possible to find out at runtime (i.e. in spec_helper.rb) what kind of spec is being executed (i.e. request, functional etc)?
Many Thanks!
If you're looking to add something like a before filter for only a certain type of spec, consider writing:
RSpec.configure do |config|
config.before(:each, type: :request) do
# request-spec only before hook
end
end
If you're trying to do something more complicated, you can access the current example's type via example.metadata[:type].
The way the rspec/rails project makes this distinction is by checking the file path.
For instance, a request spec will have a file path that matches /spec\/request/.