This is my first project in rails, and for some reason I fail to create my first unit test for my controller.
Basically, I have a main object Election, and each election may contain many voters.
The voters are created from a comma separated list of emails.
In this test, I want to test several lists of emails, to be sure that they are ingested correctly.
But for a reason I can't really grasp, my Voter model is not detected by my controller test.
So here is the related part of the code :
voters_controller_test.rb
require 'test_helper'
class VotersControllerTest < ActionController::TestCase
test "should add new voters" do
assert_difference('Voters.count', 2) do
post :create, voter: {election_id: 1, email_list: "me#me.fr, you#you.com"}
end
end
end
voter.rb
class Voter < ActiveRecord::Base
attr_accessor :email_list
belongs_to :election
validates :email, presence: true, :email => true
validates_uniqueness_of :email, :scope => [:election_id]
end
and the controller, voters_controller.rb
class VotersController < ApplicationController
def index
#election = Election.find(params[:election_id])
end
def create
#election = Election.find(params[:election_id])
emails = voter_params[:email_list].squish.split(',')
emails.each { |email| #voter = #election.voters.create(:email =>email) }
redirect_to election_voters_path(#election)
end
private
def voter_params
params.require(:voter).permit(:email_list)
end
end
I should probably mention that my application works fine, and that only the test is failing.
The exact error message is :
Run options: --seed 24993
# Running:
E.
Finished in 0.098560s, 20.2922 runs/s, 10.1461 assertions/s.
1) Error:
VotersControllerTest#test_should_add_new_voters:
NameError: uninitialized constant VotersControllerTest::Voters
/home/jll/Documents/01_perso/00_myelections/test/controllers/voters_controller_test.rb:6:in `block in <class:VotersControllerTest>'
This it is my very first ruby test, I heavily inspired myself from the rails testing tutorial.
Could you please provide me some insight on what I do wrong?
Thanks!
You're trying to assert the difference on the Voters model instead of the Voter model. This is what the code should look like.
assert_difference('Voter.count', 2) do
...
end
Remember, models will bear the singular version of the resource name while controllers will bear the plural name. E.g. The model is Voter while the controller is VotersController.
Related
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.
I'm building a Rails (4.1.0) app that runs a poll. each poll has n Matchups with n Seats. Here are my models:
class Matchup < ActiveRecord::Base
has_many :seats, dependent: :destroy
def winning_seat
seats.sort { |a,b| a.number_of_votes <=> b.number_of_votes }.last
end
end
class Seat < ActiveRecord::Base
belongs_to :matchup
validates :matchup, presence: true
validates :number_of_votes, presence: true
def declare_as_winner
self.is_winner = true
self.save
end
end
My specs for Matchup and Seat pass without issue. At the end of a poll, I need to display the winner. I am using a Sidekiq worker to handle the end of the poll. It does many things, but here's the code in question:
class EndOfPollWorker
include Sidekiq::Worker
def perform(poll_id)
poll = Poll.where(:id poll_id)
poll.matchups.each do |matchup|
# grab the winning seat
winning_seat = matchup.winning_seat
# declare it as a winner
winning_seat.declare_as_winner
end
end
end
The spec for this worker doesn't pass:
require 'rails_helper'
describe 'EndOfPollWorker' do
before do
#this simple creates a matchup for each poll question and seat for every entry in the matchup
#poll = Poll.build_poll
end
context 'when the poll ends' do
before do
#winners = #poll.matchups.map { |matchup| matchup.seats.first }
#losers = #poll.matchups.map { |matchup| matchup.seats.last }
#winners.each do |seat|
seat.number_of_votes = 1
end
#poll.save!
#job = EndOfPollWorker.new
end
it 'it updates the winner of each matchup' do
#job.perform(#poll.id)
#winners.each do |seat|
expect(seat.is_winner?).to be(true)
end
end
it 'it does not update the loser of each matchup' do
#job.perform(#poll.id)
#losers.each do |seat|
expect(seat.is_winner?).to be(false)
end
end
end
end
end
end
When I run this spec, I get:
EndOfPollWorker when poll ends it updates the winner of each matchup
Failure/Error: expect(seat.is_winner?).to be(true)
expected true
got false
My specs for the Seat and Matchup models pass just fine. I cut a lot of the test code out, so excuse any mismatched tags, assume that's not the problem!
Also, when the workers actually run in development mode, the seats.is_winner attribute isn't actually updated.
Thanks
Sidekiq has nothing to do with your problem. You're directly calling perform so the issue is with rspec and activerecord. For instance, pull the code out of the perform method and put it directly in the spec, it should still fail.
I suspect the instances are stale and need to be #reload'd from the database to pick up the changes done in #perform.
I'm trying to delete the embedded document from a parent document, and then add another embedded document but I am getting an error. Is my syntax not correct?
Error
NoMethodError: undefined method `create' for []:Array
Code
u = User.last
u.classes.destroy_all
u.classes.create(:name => "Philsophy") # I get the error right at this line
Ruby on Rails 3.0.3
Mongoid 2.0.0.beta.20
Thanks!
The relationship method classes returns an Array, not your base class.
To create and embed an object, you need to call the new method on the class,
then append it to classes.
Here is a working example that is probably close to what you intended. Hope that it helps you move on.
class User
include Mongoid::Document
field :name, type: String
embeds_many :classes, class_name: 'MyClass'
end
class MyClass
include Mongoid::Document
field :name, type: String
embedded_in :user
end
test/unit/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
User.delete_all
end
test "embedded doc" do
User.create(name: 'Gary')
assert_equal(1, User.count)
u = User.last
u.classes.destroy_all
puts "u.classes.class: #{u.classes.class}"
u.classes << MyClass.new(:name => 'Philosophy')
user = User.find(u.id)
assert_equal('Philosophy', user.classes.first.name)
puts user.to_json
end
end
test output
Run options: --name=test_embedded_doc
# Running tests:
u.classes.class: Array
{"_id":"4fc62aeb7f11baa5b0000001","classes":[{"_id":"4fc62aeb7f11baa5b0000002","name":"Philosophy"}],"name":"Gary"}
.
Finished tests in 0.009929s, 100.7151 tests/s, 201.4302 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
https://mongoid.github.io/old/en/mongoid/v3/relations.html#embeds_many
is helpful and points to many ways where you can add new objects to an existing object such as:
band.albums.build(name: "Violator")
band.albums.new(name: "Violator")
and if you have multiple objects
band.albums.concat(
Album.new(name: "Violator"),
Album.new(name: "101")
)
In my model, I dynamically create some methods based on database records:
class Job < ActiveRecord::Base
belongs_to :job_status
# Adds #requisition?, #open?, #paused?, #closed?
class_eval do
JobStatus.all.each do |status|
unless method_defined? "#{status.name.downcase}?"
define_method("#{status.name.downcase}?") do
job_status_id == status.id
end
end
end
end
end
class JobStatus < ActiveRecord::Base
has_many :jobs
end
The job_statuses table contains some seed data, so is not going to be frequently changing, but in case I ever need to add new statuses, I don't have to add more code to get a boolean method for the new status.
However, I am not sure how to test these methods, because when rspec starts the job_statuses table is obviously empty, and when the JobStatus objects are created, Job gets initialized, but since no objects exist yet, it doesn't create any methods, and my tests fail because the methods don't exist.
Note that I am using rspec with spork & guard, and using database-cleaner with the truncation strategy (as per Railscast #257, since I'm using Selenium), so that probably complicates matters.
The solution I came up with was to abstract the creation of runtime methods out into a library file, and then in my test file, remove and redeclare my class before each test, and reload the actual class (and blueprints) at the end of the suite:
describe AssociationPredicate do
before(:all) do
["Continuous", "Standard"].each { |type| JobType.create!(:job_type => type) }
["Requisition", "Open", "Paused", "Closed"].each { |status| JobStatus.create!(:job_status => status) }
end
after(:all) do
DatabaseCleaner.clean_with :truncation, :only => %w( job_types job_statuses )
# Reload Job model to remove changes
Object.send(:remove_const, 'Job')
load 'job.rb'
load 'support/blueprints.rb'
end
before(:each) do
Object.send(:remove_const, 'Job')
# Redefine Job model for testing purposes
class Job < ActiveRecord::Base
belongs_to :job_type
belongs_to :job_status
has_many :job_applications
end
end
it "should add methods when included" do
Job.send(:association_predicate, :job_type)
job.should respond_to(:continuous?)
job.should respond_to(:standard?)
end
end
This way, I create a basic class for each test, add the runtime methods as necessarily, and return to the actual class when I'm done.
Try with enumerize gem. This make your status field like enumerator and build the "#{status.name.downcase}?" for your models. This gem came with it's own rspec-matchers making easiest your unit test.
I'm having a problem testing for methods that correspond to the database columns of an ActiveRecord. Take any model you've got (in my case Document), and do the following:
$ rails c
Loading development environment (Rails 3.0.9)
>> Document.method_defined?(:id)
=> false
>> Document.new
=> #<Document id: nil, feed_id: nil >
>> Document.method_defined?(:id)
=> true
There is obviously some ActiveRecord lifecycle work going on as a prerequisite to .new() that's adding all the database columns as methods to the class.
Where this really complicates things is during unit testing. I'd like have a class that in runtime accepts ActiveRecord classes and does some validation on them
For example
class Foo < ActiveRecord::Base
def work1
# something
end
end
class Command
def operates_on(clazz, method)
# unless I add a "clazz.new" first, method_defined? will fail
# on things like :id and :created_at
raise "not a good class #{clazz.name}" if ! clazz.method_defined?(method)
# <logic here>
end
end
describe Command do
it "should pass if given a good class" do
Command.new.operates_on(Foo,:work1)
end
it "should pass if given a database column" do
# this FAILS
Command.new.operates_on(Foo,:id)
end
it "should raise if given an invalid class/method combo" do
lambda { Command.new.operates_on(Foo,:non_work) }.should raise_error
end
end
What can I do to assert (other than making a junk instance with .new()) that the ActiveRecord has done its initialization?
This appears to be a bug but isn't likely going to be fixed soon.
Inconsistent method_defined? behaviour
I found that .attribute_method? works on the class to do what you need. You can find more information at ActiveRecord::AttributeMethods::ClassMethods