How to test Rails model associations - ruby-on-rails

Trying to test a module. It works when executed in rails console, but not when written as a test. Suppose the following:
MyModel
a) has_many :my_other_model
MyOtherModel
a) belongs to :my_model
Module example:
module MyModule
def self.doit
mine = MyModel.first
mine.my_other_models.create!(attribute: 'Me')
end
end
Now test:
require 'test_helper'
class MyModuleTest < ActiveSupport::TestCase
test "should work" do
assert MyModule.doit
end
end
Returns:
NoMethodError: NoMethodError: undefined method `my_other_models' for nil:NilClass
Now try the same thing in the console:
rails c
MyModule.doit
Works just fine. But why not as a test?

Your test database is empty when you run this test, so calling MyModel.first is going to return nil, then you try to chain an unknown method to nil. What you'll probably want for your test suite is a fixture, which is just sample data. For now, you you can just create the first instance to get the test to work.
test "should work" do
MyModel.create #assuming the model is not validated
assert MyModule.doit
end
You could also refactor your module. Adding if mine will only try to create the other models if mine is not nil. That would get the test to pass, but negates the purpose of your test.
def self.doit
mine = MyModel.first
mine.my_other_models.create!(attribute: 'Me') if mine
end

Related

How to run method in test

I simply want to run a method in my test and see if it works.
I tried the following line of code in my testing class:
UserPostcodesImport.add_postcodes_from_csv
My user_postcodes_import_test.rb:
require "test_helper"
require "user_postcodes_import"
class UserPostcodesImportTest < ActiveSupport::TestCase
it "works" do
UserPostcodesImport.add_postcodes_from_csv
end
end
My user_postcodes_import:
class UserPostcodesImport
class << self
def add_postcodes_from_csv
puts "it works"
end
end
end
I expect the console to print "it works" but it prints the error:
NoMethodError: undefined method `add_postcodes_from_csv'
So testing doesn't really work like that. What you need to do in this case is take a look at the test calls and do something like this
test "the truth" do
assert true
end
so you might have
class UserPostcodesImportTest < ActiveSupport::TestCase
it "works" do
test_string = UserPostcodesImport.add_postcodes_from_csv
assert !test_string.blank?
end
end
If you're using rspec, it might look like this:
class UserPostcodesImportTest < ActiveSupport::TestCase
{subject = UserPostcodesImport}
it "works" do
expect (subject.add_postcodes_from_csv).to_not be_nil
end
end
something like that...check rspecs syntax here: https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
The critical part of that is the assert, which is basically what triggers the test to run. You're asking "when I do THIS, does it return true?"
I'd start by looking here: https://guides.rubyonrails.org/testing.html in order to get a better sense of testing best practices.

Static method in Unittest not found - NoMethodError - Ruby

I have some unit tests which try to test my API:
class ClassToBeTested
def self.something_first
# Do Something
end
def self.something_second
# Do Something
end
end
I call them in the testing class then as following:
class MyTest < ActionDispatch::IntegrationTest
test 'should get something_first' do
assert ClassToBeTested.something_first
end
test 'should get something_second' do
assert ClassToBeTested.something_second
end
end
Which ends up throwing the following error:
Error: MyTest#test_should_get_something_first: NoMethodError:
undefined method something_first' for ClassToBeTested
test/services/my_test.rb:89:inblock in '
bin/rails test test/services/my_test.rb:88
I tried around a lot but I can't find what the issue is.
The Issue I was faced was that a Library I was using had a class with the same name.
So my Unittest was calling this other class and not the class I defined, to solve this I added:
load 'ClassToBeTested.rb' in the header of the testing class.
A cleaner way probably is to define the ClassToBeTested in a Module and use the namespace to call it.

How to test non-ActiveRecord models in rails?

How do I test a non-ActiveRecord model class in Rails? The code that works in the console does not seem to translate to the test suite. Methods are not available and the whole thing just doesn't seem to work!
class CalendarEvent
def test
'Hello World'
end
end
When I fire up the console, this works:
irb> cal_event = CalendarEvent.new
=> #<CalendarEvent:0x007fb5e3ee9fd0>
irb> cal_event.test
=> "Hello World"
However, when I write a test it seems the model is not loading. None of the functions are available.
class CalendarEvent < ActiveSupport::TestCase
include TestApiHelperPackage
test 'validate hello world' do
cal_event = CalendarEvent.new
assert_equal cal_event.test, 'Hello world'
end
end
Seems like it isn't grabbing the model. Do I have to not inherit from ActiveSupport::TestCase ?
I am surprised that it even runs. You declare the class CalendarEvent in the test suit. Which obviously has no test method. Why would you name it the exact same way as the tested class? Just give it another name:
# ⇓⇓⇓⇓ HERE
class CalendarEventTest < ActiveSupport::TestCase
include TestApiHelperPackage
test 'validate hello world' do
cal_event = CalendarEvent.new
assert_equal cal_event.test, 'Hello world'
end
end
And everything should go fine. Possible you want to require 'calender_event' in your test_heler.rb as well.

MyClass.inspect return incorrect class when I run whole test suite

MyClass.inspect return incorrect class when I run whole test suite.
Problem:
I have User::CreditCard and ActiveMerchant::Billing::CreditCard classes in project. Last from activemerchant gem.
When I run single spec(rspec spec/models/user/credit_card_spec.rb) then it works correctly.
When I run whole suite(rspec spec) then spec fails with undefined method..., it doesn't matter. The problem is that in this case, my CreditCard class is not mine!!!
When I run single spec and do puts User::CreditCard.inpsect(or just p User::CreditCard, or in pry just User::CreditCard) then it returns User::CreditCard as expected.
When I run whole suite and do p User::CreditCard inside spec then it returns ActiveMerchant::Billing::CreditCard.
Background:
If you don't want to read "background" then be sure that there are NOTE in the end
I'm working with legacy code. So I don't fully know all parts of the image.
I want to create Value Object for credit card in my User. So I've create new tableless model(note the path and class name):
#app/models/user/credit_card.rb
class User::CreditCard
include ActiveModel::Model
delegate :card_number, :card_expiration, :card_type, to: :subscription
def initialize(subscription)
#subscription = subscription || Subscription.new
end
private
attr_reader :subscription
end
Of course I have User model:
#app/models/user.rb
class User
...
has_one :subscription
...
def credit_card
#credit_card ||= User::CreditCard.new(subscription)
end
end
My specs for user/credit_card:
#spec/models/user/credit_card_spec.rb
require 'spec_helper'
# require 'user/credit_card' # if I include this then it works correct
RSpec.describe User::CreditCard, type: :model do
let(:subscription) { build :subscription }
let(:credit_card) do
p User::CreditCard # this result depends on whole/not whole suite run...
# rspec spec => ActiveMerchant::Billing::CreditCard
# rspec spec/models/user => User::CreditCard
User::CreditCard.new(subscription)
end
it 'should delegate alowed messages to user subscription' do
%w[card_number card_expiration card_type].each do |attr|
expect(credit_card.public_send(attr)).to eql subscription.public_send(attr)
end
end
it 'disallow another methods' do
expect { credit_card.unexisted_method }.to raise_error(NoMethodError)
end
end
NOTE:
in spec I can require 'user/credit_card' and then it will work. But why it does not work without it?
Can it be a problem in another places? For example in controllers or somewhere else?
This is a glitch of rails autoloading + ruby constant resolution.
class C; end
CONST = 42
C::CONST
#⇒ (pry):3: warning: toplevel constant CONST referenced by C::CONST
#⇒ 42
Surprisingly enough, CONST was resolved. That is because of Ruby constant resolution algorithm.
One has two options to fix the problem: either to give a different name to the class User::CreditCard or to make sure it’s loaded. Otherwise Rails finds the constant CreditCard in ActiveMerchant::Billing namespace and is happy with using it.

rspec - how to test for a model attribute that is not a database column

I have an Active Record based model:- House
It has various attributes, but no formal_name attribute.
However it does have a method for formal_name, i.e.
def formal_name
"Formal #{self.other_model.name}"
end
How can I test that this method exists?
I have:
describe "check the name " do
#report_set = FactoryGirl.create :report_set
subject { #report_set }
its(:formal_name) { should == "this_should_fail" }
end
But I get undefined method 'formal_name' for nil:NilClass
First you probably want to make sure your factory is doing a good job creating report_set -- Maybe put factory_girl under both development and test group in your Gemfile, fire up irb to make sure that FactoryGirl.create :report_set does not return nil.
Then try
describe "#formal_name" do
let(:report_set) { FactoryGirl.create :report_set }
it 'responses to formal_name' do
report_set.should respond_to(:formal_name)
end
it 'checks the name' do
report_set.formal_name.should == 'whatever it should be'
end
end
Personally, I'm not a fan of the shortcut rspec syntax you're using. I would do it like this
describe '#formal_name' do
it 'responds to formal_name' do
report_set = FactoryGirl.create :report_set
report_set.formal_name.should == 'formal_name'
end
end
I think it's much easier to understand this way.
EDIT: Full working example with FactoryGirl 2.5 in a Rails 3.2 project. This is tested code
# model - make sure migration is run so it's in your database
class Video < ActiveRecord::Base
# virtual attribute - no table in db corresponding to this
def embed_url
'embedded'
end
end
# factory
FactoryGirl.define do
factory :video do
end
end
# rspec
require 'spec_helper'
describe Video do
describe '#embed_url' do
it 'responds' do
v = FactoryGirl.create(:video)
v.embed_url.should == 'embedded'
end
end
end
$ rspec spec/models/video_spec.rb # -> passing test

Resources