rails rspec - how to check for a model constant? - ruby

How can I do something like:
it { should have_constant(:FIXED_LIST) }
In my model (active record) I have FIXED_LIST = 'A String'
It's not a db attribute or a method and I haven't been able to use responds_to or has_attribute to test for it (they fail). What can I use the to check for it. - btw I have the shoulda-matchers installed.

Based on David Chelimsky's answer I've got this to work by slightly modifying his code.
In a file spec/support/utilities.rb (or some other in spec/support) you can put:
RSpec::Matchers.define :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
Note the use of "RSpec::Matchers.define" in stead of "matchers"
This allows to test for constants in your specs, like:
it "should have a fixed list constant" do
YourModel.should have_constant(:FIXED_LIST)
end
Note the use of "have_constant" in stead of "have_const"

It reads a little silly, but:
describe MyClass do
it { should be_const_defined(:VERSION) }
end
The reason is that Rspec has "magic" matchers for methods starting with be_ and have_. For example, it { should have_green_pants } would assert that the has_green_pants? method on the subject returns true.
In the same fashion, an example such as it { should be_happy } would assert that the happy? method on the subject returns true.
So, the example it { should be_const_defined(:VERSION) } asserts that const_defined?(:VERSION) returns true.

If you want to say have_constant you can define a custom matcher for it:
matcher :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
MyClass.should have_const(:CONST)
If you're trying to use the one-liner syntax, you'll need to make sure the subject is a class (not an instance) or check for it in the matcher:
matcher :have_constant do |const|
match do |owner|
(owner.is_a?(Class) ? owner : owner.class).const_defined?(const)
end
end
See http://rubydoc.info/gems/rspec-expectations/RSpec/Matchers for more info on custom matchers.
HTH,
David

Another option to simply make sure the constant is defined – not worrying about what it's defined with:
it 'has a WHATEVER constant' do
expect(SomeClass::WHATEVER).not_to be_nil
end

A warning to anyone trying to test that constants are defined: If your code references an undefined constant while defining a class, then your specs will crash before they get to your test.
This can lead you to believe that
expect { FOO }.to_not raise_error
is failing to catch the NameError, because you'll get a big stack trace, instead of a nice "expected not to raise error, but raised NameError."
Amidst the huge stack trace, it can be difficult to notice that your test is actually crashing on line 1: requre "spec/spec_helper" because your entire application is failing to load before it gets to your actual test.
This can happen if you have dynamically defined constants, such as is done by ActiveHash::Enum, and you then use them in the definition of another constant. Don't bother testing that they exist, every spec in your app will crash if one of them fails to be defined.

You could use
defined? YOUR_MODEL::FIXED_LIST

In RSpec 2, I was able to get this to work in one line as follows:
it { subject.class.should be_const_defined(:MY_CONST) }
That is, check against the class, instead of the instance.

In My model
class Role < ActiveRecord::Base
ROLE_ADMIN = "Administrador"
end
In My rspec
RSpec.describe Role, type: :model do
let(:fake_class) { Class.new }
describe "set constants" do
before { stub_const("#{described_class}", fake_class) }
it { expect(described_class::ROLE_ADMIN).to eq("Administrador") }
end
end

For ruby 2.1.5 and rspec 3.5.0 I am able to test that constant SEARCH_CHARS_TO_IGNORE is defined in the class DiffAlertsDatatable as follows:
expect(DiffAlertsDatatable.const_defined?(:SEARCH_CHARS_TO_IGNORE)).to eq(true)

Related

What is the correct way to define an alias to `after` in rspec?

Normally the before and after hooks are assumed to be "initializing" and "cleanup" code respectively. They are supposed to happen "outside of the tests themselves".
I find myself in a situation in which I want to use after as the last step of all the tests in a context. But since after is usually meant to be "cleanup", I am afraid that my tests won't be very explicit. Here's a sample:
describe "when removing" do
let!(:request) do
stub_request(:delete, "http://localhost:4567/containers/#{container.id}").
to_return(status: 200)
end
# returns an http response
subject { client.remove(container.id) }
it { should be }
it { should include('id' => container.id) }
after { expect(request).to have_been_made }
end
I would like to rename that last after to something more explicit, like invariant, to indicate that it is part of the test. I have tried doing this on my spec helper:
# spec_helper.rb
Rspec.configure do |c|
...
end
RSpec::Core::Hooks.class_eval do
alias_method :invariant, :after
end
requiring spec_helper does not seem to throw any errors, but when I run the tests replacing after with invariant I get "undefined method 'invariant' for #<Class:0x007f9872b46588> (NoMethodError)" when running the tests.
It seems there is no easy way to do this, just use before/after

RSpec 'specify' passes with explicit subject, but not with implicit subject

I thought I understood how implicit subjects work in RSpec, but I don't.
Why is it that in the following example, the first spec with an explicit subject passes, but the second spec using an implicit subject fails with "undefined method `matches' for #":
class Example
def matches(str) ; true ; end
end
describe Example do
subject { Example.new }
specify { subject.matches('bar').should be_true }
it { matches('bar').should be_true }
end
(I'm using rspec 1.3, but I verified the same behavior with 2.10.1.)
Step back to some basic ruby: You're basically calling self.matches, and self in this case is an RSpec example.
You can call things like "should" on this example, with parameters, so you might try something like:
it { should matches('bar') }
but this will fail; there's no method matches on self still!
In this case, though, the subject really is the matches method, not the Example instance. So, if you want to continue using the implicit subject, your tests might be something like:
class Example
def matches(str) ; str == "bar" ; end
end
describe Example do
describe "#matches" do
let(:method) { Example.new.method(:matches) }
context "when passed a valid value" do
subject { method.call("bar") }
it { should be_true }
end
context "when passed an invalid value" do
subject { method.call("foo") }
it { should be_false }
end
end
end
I don't think you can call any methods of implicit subject. Implicit subject meanings you don't need to specify the subject, but if you want call any method you need to specify the subject.
Although Chris provided very nice answer, I recommend you to take a look at this blog post: http://blog.davidchelimsky.net/2012/05/13/spec-smell-explicit-use-of-subject/

Ruby Double (RR) How do I set up expectations for a block of method calls passed to block argument method?

In my code I have code similar to the following contrived example.
class Excel
def self.do_tasks
with_excel do |excel|
delete_old_exports
export_images(excel)
export_documents(excel)
end
end
def with_excel
excel = WIN32OLE.connect('Excel.Application')
begin
yield excel
ensure
excel.close()
end
end
end
Now, I want to write a test for the 'do_tasks' method, where I set up expectations for the method calls and see if those expectations are fulfilled.
I tried the following approach (with shoulda-context and test-unit). However,the expectations fail for the three last mocks (the mocks do not get called).
class ExcelTest < ActiveSupport::TestCase
should "call the expected methods" do
mock.proxy(Excel).with_excel
mock(Excel).delete_old_exports
mock(Excel).export_images.with_any_args
mock(Excel).export_documents.with_any_args
Excel.do_tasks
end
end
Any pointers on how to test this sort of code would be much appreciated!
An older question, but I've just been doing some work on some similar code with rr and thought I'd throw in an answer.
The following test will do what you asked (using RR and TestUnit):
describe Excel do
describe '.do_tasks' do
let(:excel_ole) { mock!.close.subject }
before do
stub(WIN32OLE).connect('Excel.Application') { excel_ole }
mock(Excel).delete_old_exports
mock(Excel).export_images(excel_ole)
mock(Excel).export_documents(excel_ole)
end
it 'calls the expected methods' do
Excel.do_tasks
assert_received(Excel) { |subject| subject.delete_old_exports }
end
end
end
It uses RR's "spy" doubles - see https://github.com/rr/rr#spies
However, in the case of the sample code you provided, the fact that the methods you want to test are inside a block is an implementation detail and shouldn't be implicitly tested (this can lead to brittle tests). The test above shows this, the with_excel method is not mocked (incidentally, this should be defined as self.with_excel for the code to work). The implementation could be refactored so that the WIN32OLE initialisation and teardown happens inline in the .do_tasks method and the test would still pass.
On another note, it may be a side effect of the contrived example, but in general it's a bad idea to test non-public methods. The methods delete_old_exports, export_images and export_documents look like they should perhaps be factored out to collaborators.

Is there a way to mock/stub "puts" in Rails

I am printing some custom messages in my application using the puts command. However, I do not want these to be appearing in my Test Output. So, I tried a way to stub puts as shown below. But it still outputs my messages. What am I doing wrong ?
stubs(:puts).returns("") #Did not work out
Object.stubs(:puts).returns("") #Did not work out either
puts.stubs.returns "" #Not working as well
Kernel.stubs(:puts).returns "" #No luck
I am using Test::Unit
You probably need to stub it on the actual instance that calls puts. E.g. if you're calling puts in an instance method of a User class, try:
user = User.new
user.stubs(:puts)
user.some_method_that_calls_puts
This similarly applies to when you're trying to test puts in the top-level execution scope:
self.stubs(:puts)
What I would do is define a custom log method (that essentially calls puts for now) which you can mock or silence in test quite easily.
This also gives you the option later to do more with it, like log to a file.
edit: Or if you really want to stub puts, and you are calling it inside an instance method for example, you can just stub puts on the instance of that class.
Using Rails 5 + Mocha: $stdout.stubs(puts: '')
So the comments to the original post point to the answer:
Kernel.send(:define_method, :puts) { |*args| "" }
Instead of silencing all output, I would only silence output from the the particular objects that are putsing during your tests.
class TestClass
def some_method
...
puts "something"
end
end
it "should do something expected" do
TestClass.send(:define_method, :puts) { |*args| "" }
test_class.some_method.should == "abc123"
end

Is it possible to access the subject of the surrounding context in Rspec?

The following code doesn't work, but it best show what I'm trying to achieve
context "this context describes the class" do
subject do
# described class is actually a module here
c = Class.new.extend(described_class)
c.some_method_that_has_been_added_through_extension
c
end
# ... testing the class itself here ...
context "instances of this class" do
subject do
# this doesn't work because it introduces a endless recursion bug
# which makes perfectly sense
subject.new
end
end
end
I also tried to use a local variable in the inner context that I initialized
with the subject, but no luck. Is there any way I can access the subject of a outer scope from within my subject definition in the inner scope?
Using #subject can sometimes cause trouble. It is "primarily intended" for use with the short-hand checks like #its.
It also can make example harder to read, as it can work to mask the name/intent of what you testing. Here's a blog post that David Chelimsky wrote on the topic of #subject and #let and their role in revealing intention: http://blog.davidchelimsky.net/blog/2012/05/13/spec-smell-explicit-use-of-subject/
Try using let, instead
https://www.relishapp.com/rspec/rspec-core/v/2-10/docs/helper-methods/let-and-let
Here is how I would most likely write it.
context "this context describes the class" do
let(:name_of_the_module) { Class.new.extend(described_class) }
before do
c.some_method_that_has_been_added_through_extension
end
# ... testing the class itself here ...
context "instances of this class" do
let(:better_name_that_describes_the_instance) { klass.new }
# ... test the instance
end
end
SIDENOTE
You might want to revisit whether you want to use subject at all. I prefer using #let in almost all cases. YMMV
Something that obviously works is using an instance variable in the inner context and initializing it not with the subject but subject.call instead. Subjects are Procs. Hence, my first approach didn't work.
context "instances of this class" do
klass = subject.call
subject { klass.new }
end
I have been looking for a solution to this, but for different reasons. When I test a method that could return a value or raise an error, I often have to repeat the subject in two contexts, once as a proc for raise_error and once normally.
What I discovered is that you can give subjects names, like lets. This let's you reference an named subject from an outer scope within a new subject. Here's an example:
describe 'do_some_math' do
let!(:calculator) { create(:calculator) }
# proc to be used with raise_error
subject(:do_some_math) {
-> { calculator.do_some_math(with, complicated, args) }
}
context 'when something breaks' do
it { is_expected.to raise_error } # ok
end
context 'when everything works' do
# here we call the named subject from the outer scope:
subject { do_some_math.call } # nice and DRY
it { is_expected.to be_a(Numeric) } # also ok!
end
end

Resources