I've attempted to create a model, which needs to pass a series of validation tests in RSpec. However, I constantly get the error
expected #<Surveyor::Answer:0x0055db58e29260 #question=#<Double Surveyor::Question>, #value=5> to respond to `valid?`
My understanding (from here) was that 'valid?' checks that no errors were added to the model. I can't find any errors, however the message above persists.
This is my model
module Surveyor
class Answer
attr_accessor :question, :value
def initialize(params)
#question = params.fetch(:question)
#value = params.fetch(:value)
end
end
end
And the class Question
module Surveyor
class Question
attr_accessor :title, :type
def initialize(params)
#title = params.fetch(:title, nil)
#type = params.fetch(:type)
end
end
end
And this is the test I am attempting to pass
RSpec.describe Surveyor::Answer, '03: Answer validations' do
let(:question) { double(Surveyor::Question, type: 'rating') }
context "question validation" do
context "when the answer has a question" do
subject { described_class.new(question: question, value: 5) }
it { should be_valid }
end
end
Is my understanding of 'valid?' correct? Am I able to look at 'valid?' and perhaps see where I'm going wrong?
RSpec doesn't actually have a matcher called be_valid, instead it has some dynamic predicate matchers:
For any predicate method, RSpec gives you a corresponding matcher. Simply prefix the
method with be_ and remove the question mark. Examples:
expect(7).not_to be_zero # calls 7.zero?
expect([]).to be_empty # calls [].empty?
expect(x).to be_multiple_of(3) # calls x.multiple_of?(3)
so by calling it { should be_valid }, your subject has to respond to a valid? method. If you're testing an ActiveRecord model, those have a valid? method, but your model does not. So, if you want to test that your Answer is valid, you need to decide "what is a valid answer?" and write a method that checks for those conditions. If you want an API similar to Rails model, you might be interested in using ActiveModel::Validations
Related
After reading this question I really do not like the answer.
Rails / RSpec: How to test #initialize method?
Maybe I am having a third scenario. This is what I have now, inspired by second code from that answer.
# Picture is collection of SinglePictures with same name and filename,
# but different dimensions
class Picture
attr_accessor :name, :filename
attr_reader :single_pics, :largest_width
def initialize(name, filename, dimensions=nil)
#largest_width = 0
#single_pics = {}
add_single_pics(dimensions) if dimensions
end
def add_single_pics(max_dimension)
# logic
end
end
describe '#initialize' do
it 'should not call add_single_pics if dimensions is not given' do
subject = Picture.new('Test Picture', 'Test-Picture')
expect(subject.largest_width).to eq 0
end
it 'should call add_single_pics if dimensions are given' do
subject = Picture.new('Test Picture', 'Test-Picture', 1920)
expect(subject.largest_width).to eq 1920
end
end
I really don't like this because I am testing the functionality of add_single_pics in #initialize tests. I would like to write somehow this in spec:
expect(subject).not_to have_received(:add_single_pics)
expect(subject).to have_received(:add_single_pics)
But I get
Expected to have received add_single_pics, but that object is not a spy
or method has not been stubbed.
Can I fix this somehow?
Spies are an alternate type of test double that support this pattern
by allowing you to expect that a message has been received after the
fact, using have_received.
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/spies
Only spy object can store the method calls. To test your real class in the way that you want, you have to use expect_any_instance_of statement before the class will be initialized:
expect_any_instance_of(Picture).to receive(:add_single_pics)
Picture.new('Test Picture', 'Test-Picture')
In this case your add_single_pics method will be called, but its logic will not be run, if you need to run it you need to call the and_call_original method on the matcher:
expect_any_instance_of(Picture).to receive(:add_single_pics).and_call_original
I have a simple MySQL wrapper class which will run a query and return results.
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
I want to test some stuff involving the results of the query, but I don't want to actually connect to a database to get the results. I tried this test, but it doesn't work:
RSpec.describe Rsql do
it "does it" do
mock_database = double
rsql = Rsql.new(mock_database)
mock_mysql_client = double
allow(mock_mysql_client).to receive(:query).and_return({"1" => 1})
allow_any_instance_of(Mysql2::Client).to receive(:new).and_return(mock_mysql_client)
expect(rsql.execute_query).to eq({"1" => 1})
end
end
Replacing allow_any_instance_of() with allow() works. I was under the impression that allow_any_instance_of() was some kind of a global "pretend this class behaves in this way across the entire program" whereas allow() is for specific instances of a class.
Can someone explain this behavior to me? I'm new to Rspec, so I apologize if this answer is blatantly obvious. I tried searching for the answer, but I couldn't come up with the right search string to find one. Maybe I don't know enough to know when I've found it.
As of RSpec 3.3 , any_instance is deprecated and not recommended to use in your tests.
From the docs:
any_instance is the old way to stub or mock any instance of a class
but carries the baggage of a global monkey patch on all classes. Note
that we generally recommend against using this feature.
You should only need to use allow(some_obj) going forward and the documentation has some great examples (see here).
Such as:
RSpec.describe "receive_messages" do
it "configures return values for the provided messages" do
dbl = double("Some Collaborator")
allow(dbl).to receive_messages(:foo => 2, :bar => 3)
expect(dbl.foo).to eq(2)
expect(dbl.bar).to eq(3)
end
end
Edit, if you really want to use any_instance, do so like this:
(Mysql2::Client).allow_any_instance.to receive(:something)
Edit2, your exact stub doesn't work because you're not stubbing an instance, you're stubbing before the object is initialized. In that case you would do allow(Mysql2::Client).to receive(:new).
this Rsql class seems a service
class Rsql
def initialize(db)
#client = Mysql2::Client
#db = db
end
def execute_query()
client = #client.new(#db)
client.query("select 1")
end
end
lets create a test for it, now we should to test this function execute_query with subject ()
and to create clients in db we can use let! like this
let!(:client1) do
FactoryBot.create(...
with this we should not use double or something
require 'rails_helper'
RSpec.describe RsqlTest do
subject(:clients) do
Rsql.execute_query()
end
context 'select' do
let!(:client1) do
FactoryBot.create(...
end
it 'should return records' do
expect(clients).to include(client1)
end
end
end
I am trying to use some functionality in ActiveModel but I'm having trouble making everything work. I've included my class file and the test I'm running.
The test is failing with:
': undefined method `attr_accessible
I really don't know why, since MassAssignmentSecurity will bring that in and it is in fact running. I've also tried to include all of ActiveModel as well but that's doesn't work either. It doesn't seem to matter if I use include or extend to bring in the MassAssignmentSecurity.
If I pass in some attributes in my test to exercise "assign_attributes" in the initialize, that fails as well. I'm fairly new to rails, so I'm hoping I'm just missing something really simple.
TIA.
Using rails 3.2.12
my_class.rb
class MyClass
include ActiveModel::MassAssignmentSecurity
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
extend ActiveSupport::Callbacks
attr_accessible :persisted, :creds
def initialize(attributes = nil, options = {})
#persisted = false
assign_attributes(attributes, options) if attributes
yield self if block_given?
end
end
my_class_spec.rb
require 'spec_helper'
describe MyClass do
before do
#testcase = MyClass.new
end
subject { #testcase }
it_should_behave_like "ActiveModel"
it { MyClass.should include(ActiveModel::MassAssignmentSecurity) }
it { should respond_to(:persisted) }
end
support/active_model.rb
shared_examples_for "ActiveModel" do
include ActiveModel::Lint::Tests
# to_s is to support ruby-1.9
ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
example m.gsub('_',' ') do
send m
end
end
def model
subject
end
end
Yikes! What a mess I was yesterday. Might as well answer my own question since I figured out my issues.
attr_accessible in MassAssignmentSecurity does not work like it does with ActiveRecord. It does not create getters and setters. You still have to use attr_accessor if you those created.
assign_attributes is a connivence function that someone wrote to wrap around mass_assignment_sanitizer and isn't something baked into in MassAssignment Security. An example implementation is below:
def assign_attributes(values, options = {})
sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
send("#{k}=", v)
end
end
I have a line in my test:
page.has_reply?("my reply").must_equal true
and to make it more readable I want to use a custom matcher:
page.must_have_reply "my reply"
Based on the docs for https://github.com/zenspider/minitest-matchers I expect I need to write a matcher which looks something like this:
def have_reply(text)
subject.has_css?('.comment_body', :text => text)
end
MiniTest::Unit::TestCase.register_matcher :have_reply, :have_reply
The problem is that I can't see how to get a reference to the subject (i.e. the page object). The docs say "Note subject must be the first argument in assertion" but that doesn't really help.
There is a little example, you can create a class which should responds to set of methods matches?, failure_message_for_should, failure_message_for_should_not.
In matches? method you can get the reference to the subject.
class MyMatcher
def initialize(text)
#text = text
end
def matches? subject
subject =~ /^#{#text}.*/
end
def failure_message_for_should
"expected to start with #{#text}"
end
def failure_message_for_should_not
"expected not to start with #{#text}"
end
end
def start_with(text)
MyMatcher.new(text)
end
MiniTest::Unit::TestCase.register_matcher :start_with, :start_with
describe 'something' do
it 'must start with...' do
page = 'my reply'
page.must_start_with 'my reply'
page.must_start_with 'my '
end
end
There are many ways to get what you want here. The easiest way is to not mess with assertions, expectations, or matchers at all and just use an assert. So, assuming you already have the has_reply? method defined, you could just use this:
assert page.has_reply?("my reply")
But, that doesn't get you the must_have_reply syntax you are asking for. And I doubt you really have a has_reply? method. So, let's start.
Your asked "how to get a reference to the subject (i.e. the page object)". In this case the subject is the object that the must_have_reply method is defined on. So, you should use this instead of subject. But its not as straightforward as all that. Matchers add a level of indirection that we don't have with the usual Assertions (assert_equal, refute_equal) or Expectations (must_be_equal, wont_be_equal). If you want to write a Matcher you need to implement the Matcher API.
Fortunately for you you don't really have to implement the API. Since it seems you are already intending on relying on Cabybara's have_css matcher, we can simply use Capybara's HaveSelector class and let it implement the proper API. We just need to create our own Matchers module with a method that returns a HaveSelector object.
# Require Minitest Matchers to make this all work
require "minitest/matchers"
# Require Capybara's matchers so you can use them
require "capybara/rspec/matchers"
# Create your own matchers module
module YourApp
module Matchers
def have_reply text
# Return a properly configured HaveSelector instance
Capybara::RSpecMatchers::HaveSelector.new(:css, ".comment_body", :text => text)
end
# Register module using minitest-matcher syntax
def self.included base
instance_methods.each do |name|
base.register_matcher name, name
end
end
end
end
Then, in your minitest_helper.rb file, you can include your Matchers module so you can use it. (This code will include the matcher in all tests.)
class MiniTest::Rails::ActiveSupport::TestCase
# Include your module in the test case
include YourApp::Matchers
end
Minitest Matchers does all the hard lifting. You can now you can use your matcher as an assertion:
def test_using_an_assertion
visit root_path
assert_have_reply page, "my reply"
end
Or, you can use your matcher as an expectation:
it "is an expectation" do
visit root_path
page.must_have_reply "my reply"
end
And finally you can use it with a subject:
describe "with a subject" do
before { visit root_path }
subject { page }
it { must have_reply("my reply") }
must { have_reply "my reply" }
end
Important: For this to work, you must be using 'gem minitest-matchers', '>= 1.2.0' because register_matcher is not defined in earlier versions of that gem.
I'm writing a few helpers to DRY up my tests. I pictured something like:
class ActiveSupport::TestCase
def self.test_presence_validation_of model, attribute
test "should not save #{model.to_s} with null #{attribute.to_s}", <<-"EOM"
#{model.to_s} = Factory.build #{model.to_sym}, #{attribute.to_sym} => nil
assert !#{model.to_s}.save, '#{model.to_s.capitalize} with null #{attribute.to_s} saved to the Database'
EOM
# Another one for blank attribute.
end
end
So that this:
class MemberTest < ActiveSupport::TestCase
test_presence_validation_of :member, :name
end
Executes exactly this at MemberTest class scope:
test 'should not save member with null name' do
member = Factory.build :member, :name => nil
assert !member.save, 'Member with null name saved to the Database'
end
Is it possible to do it this way (with a few adaptations, of course; I doubt my "picture" works), or do I have to use class_eval?
Have you seen Shoulda? It's great for testing common Rails functionality such as validations, relationships etc. https://github.com/thoughtbot/shoulda-matchers
In this case, it seems class_eval is necessary since I want to interpolate variable names into actual code.
Illustrated here.