I am new to testing in rails, I am trying to pass the test as below where it kept throwing back errors as
/scooties_coupon POST Coupon /scooties_coupons/:coupon_id with coupon coupon_id redeemed API_KEY returns redeemed id
Failure/Error: expect(json['coupon']).to eq(scooties_coupon.coupon)
expected: "6B2F5"
got: nil
(compared using ==)
Here is the code for the test I am running:
context 'with coupon coupon_id redeemed API_KEY' do
subject { post "/api/scooties_coupons/#{scooties_coupon.id}", headers: headers }
it "returns redeemed id" do
subject
expect(json['coupon']).to eq(scooties_coupon.coupon)
expect(json['redeemed']).to eq(scooties_coupon.redeemed)
end
end
my factory code:
FactoryBot.define do
factory :scooties_coupon do
coupon { "43MDA" }
redeemed { false }
email { "email#host.com" }
trait :redeemed do
redeemed { true }
end
end
end
here's the model:
class ScootiesCoupon < ApplicationRecord
has_paper_trail
before_create :set_coupon_code
after_update :send_activecamp_email_scootiescoupon
validates :email, presence: true, format: { with: /\A[^#\s]+#([^#.\s]+\.)+[^#.\s]+\z/ }
def update_redeemed(redeemed = true, email = nil)
email == self.email if email.present? || self.email.present?
self.created_at + self.expires_in.seconds < DateTime.now if self.expires_in.present?
self.update_attribute(:redeemed, redeemed)
end
private
def set_coupon_code
loop do
self.coupon = gen_coupon
break unless coupon_exists?
end
end
def coupon_exists?
self.class.exists?(coupon: coupon)
end
def gen_coupon
return SecureRandom.hex[0..4].upcase
end
end
I've tried to get rid of the validation on email but it doesn't seem to b e a major problem here, Could you give me a little idea of any other way to solve this problem?
Related
This is the topic resolver. I'm trying to update a field in learning_path table each time this query is executed for a student.
class Resolvers::Topic < GraphQL::Schema::Resolver
type Types::TopicType, null: true
description 'Returns the topic information'
argument :title, String, required: true
def resolve(title:)
user = context[:current_user]
ability = Ability.for(user)
topic = Topic.find_by(title: title)
if %w[student teacher admin].include?(user&.role) && ability.can?(:read, Topic)
if user&.role == 'student'
user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
end
if %w[student teacher].include?(user&.role) && !user&.userable&.subjects&.include?(topic.subject)
raise GraphQL::ExecutionError, 'This topic is not avaiable for you.'
end
topic
else
raise GraphQL::ExecutionError, 'You can't access this information.'
end
end
end
It works but the rspec is failling with this error:
1) LmsApiSchema topic when there's a current user returns an error if the student is not subscribe
Failure/Error: user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
NoMethodError:
undefined method `update' for nil:NilClass
topic rspec test that fails in testing:
context 'when there\'s a current user' do
let!(:student) { create(:student) }
let!(:user) { create(:user, userable: student) }
let!(:subject_a) { create(:subject) }
let!(:topic) { create(:topic, subject_id: subject_a.id) }
let!(:question) { create(:question, topic_id: topic.id) }
before do
create(:option, question_id: question.id)
prepare_context({ current_user: user })
end
it 'returns an error if the student is not subscribe' do
expect(graphql!['errors'][0]['message']).to eq('This topic is not avaiable for you.')
end
Just worked out. Added the & before setting the method for avoiding the nil error.
Figured it out thanks to this
user.userable.learning_paths.find_by(subject_id: topic.subject_id)&.update(visited_at: DateTime.now)
I am testing model using rspec and factory Girl
My model
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true
validates :name, uniqueness: true
before_destroy :safe_to_delete
def safe_to_delete
countries.any? ? false : true
end
end
My factory girl
FactoryGirl.define do
factory :currency, class: 'Currency' do
sequence(:name) { |i| "Currency-#{i}" }
end
end
My currency_spec.rb is
require 'rails_helper'
describe Currency , type: :model do
let(:currency) { create(:currency) }
let(:currency1) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject {currency}
it { should have_many(:countries) }
end
describe 'validations' do
subject {currency}
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'method save_to_delete' do
context 'case false' do
before { country.update_column(:currency_id, currency.id) }
subject { currency.destroy }
it { is_expected.to be_falsy }
end
context 'case true' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency.destroy }
it { is_expected.to be_truthy }
end
end
end
The error is:
Failure/Error: let(:currency) { create(:currency) }
ActiveRecord::RecordInvalid:
A validação falhou: Name não está disponível
Even though I disable the presence and uniqueness validations in the model, the problem continues
Who can help me
Did you properly create the migration to include the name on currencies at database level?
Because I created the migration here locally and the tests passed.
Please take a look on the below code.
It is what I did locally and is working here!
1. Migration
file: db/migrate/2021XXXXXXXXXX_create_currencies.rb
class CreateCurrencies < ActiveRecord::Migration
def change
create_table :currencies do |t|
t.string :name
end
end
end
2. Model
app/models/currency.rb
class Currency < ActiveRecord::Base
has_many :countries
validates :name, presence: true, uniqueness: true # Can be oneliner ;)
before_destroy :safe_to_delete
def safe_to_delete
countries.empty? # Much simpler, right? ;)
end
end
3. Factory
spec/factories/currency.rb
FactoryGirl.define do
factory :currency do
sequence(:name) { |i| "Currency-#{i}" }
end
end
4. Tests
spec/models/currency_spec.rb
require 'rails_helper'
describe Currency, type: :model do
let(:currency1) { create(:currency) }
let(:currency2) { create(:currency) }
let(:country) { create(:country) }
describe 'associations' do
subject { currency1 }
it { should have_many(:countries) }
end
describe 'validations' do
subject { currency1 }
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:name) }
end
describe 'when the currency is being deleted' do
context 'with countries associated' do
before { country.update_column(:currency_id, currency1.id) }
subject { currency1.destroy }
it { is_expected.to be_falsy }
end
context 'with no countries associated' do
before { country.update_column(:currency_id, currency2.id) }
subject { currency1.destroy }
it { is_expected.to be_truthy }
end
end
end
Test Execution
Finally, the tests should work correctly with the above setup!
spec/models/currency_spec.rb
rspec spec/models/currency_spec.rb
D, [2021-03-06T03:31:03.446070 #4877] DEBUG -- : using default configuration
D, [2021-03-06T03:31:03.449482 #4877] DEBUG -- : Coverband: Starting background reporting
.....
Top 5 slowest examples (0.10688 seconds, 11.4% of total time):
Currency when the currency is being deleted with countries associated should be falsy
0.04095 seconds ./spec/models/currency_spec.rb:23
Currency associations should have many countries
0.03529 seconds ./spec/models/currency_spec.rb:10
Currency when the currency is being deleted with no countries associated should be truthy
0.01454 seconds ./spec/models/currency_spec.rb:29
Currency validations should validate that :name cannot be empty/falsy
0.00812 seconds ./spec/models/currency_spec.rb:15
Currency validations should validate that :name is case-sensitively unique
0.00797 seconds ./spec/models/currency_spec.rb:16
Finished in 0.93948 seconds (files took 8.04 seconds to load)
5 examples, 0 failures
All tests passed ✅
I have a very simple static method in one of my models:
def self.default
self.find(1)
end
I'm trying to write a simple Rspec unit test for it that doesn't make any calls to the DB. How do I write a test that generates a few sample instances for the test to return? Feel free to complete this:
describe ".default" do
context "when testing the default static method" do
it "should return the instance where id = 1" do
end
end
end
The model file is as follows:
class Station < ApplicationRecord
acts_as_paranoid
acts_as_list
nilify_blanks
belongs_to :color
has_many :jobs
has_many :station_stops
has_many :logs, -> { where(applicable_class: :Station) }, foreign_key: :applicable_id
has_many :chattels, -> { where(applicable_class: :Station) }, foreign_key: :applicable_id
delegate :name, :hex, to: :color, prefix: true
def name
"#{full_display} Station"
end
def small_display
display_short || code.try(:titleize)
end
def full_display
display_long || small_display
end
def average_time
Time.at(station_stops.closed.average(:time_lapsed)).utc.strftime("%-M:%S")
end
def self.default
# referencing migrate/create_stations.rb default for jobs
self.find(1)
end
def self.first
self.where(code: Constant.get('station_code_to_enter_lab')).first
end
end
The spec file is as follows:
require "rails_helper"
describe Station do
subject { described_class.new }
describe "#name" do
context "when testing the name method" do
it "should return the capitalized code with spaces followed by 'Station'" do
newStation = Station.new(code: 'back_to_school')
result = newStation.name
expect(result).to eq 'Back To School Station'
end
end
end
describe "#small_display" do
context "when testing the small_display method" do
it "should return the capitalized code with spaces" do
newStation = Station.new(code: 'back_to_school')
result = newStation.small_display
expect(result).to eq 'Back To School'
end
end
end
describe "#full_display" do
context "when testing the full_display method" do
it "should return the capitalized code with spaces" do
newStation = Station.new(code: 'back_to_school')
result = newStation.full_display
expect(result).to eq 'Back To School'
end
end
end
describe ".default" do
context "" do
it "" do
end
end
end
end
You can use stubbing to get you there
describe ".default" do
context "when testing the default static method" do
let(:dummy_station) { Station.new(id: 1) }
before { allow(Station).to receive(:default).and_return(dummy_station)
it "should return the instance where id = 1" do
expect(Station.default.id).to eq 1
end
end
end
I would like to test my models but all informations that I could find seems to be outdated. My goal is to test each individual validation.
My model:
class Author < ActiveRecord::Base
has_and_belongs_to_many :books
before_save :capitalize_names
validates :name, :surname, presence: true, length: { minimum: 3 },
format: { with: /[a-zA-Z]/ }
private
def capitalize_names
self.name.capitalize!
self.surname.capitalize!
end
end
and my factorygirl define:
FactoryGirl.define do
factory :author do |f|
f.name { Faker::Name.first_name }
f.surname { Faker::Name.last_name }
end
end
So now, I want to test whether name is not shorter than 3 characters.
My context:
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).to
be_falsey }
end
I know it's invalid because of [FactoryGirl.build(:author, name: 'Le')] returns hash instead of boolean value. So now, how should I test it? What matcher should I use?
[SOLVED]
Use be_valid instead of be_falsey. Now it should look like :
context 'when first name is too short' do
it { expect( FactoryGirl.build(:author, name: 'Le')).not_to
be_valid }
end
I'm a beginner in ruby on rails and programming in general.
I have an assignment where I have to test my rspec model Vote, and as per instructions the test should pass.
When I run rspec spec/models/vote_spec.rb on the console, I receive the following error:
.F
Failures:
1) Vote after_save calls `Post#update_rank` after save
Failure/Error: post = associated_post
NameError:
undefined local variable or method `associated_post' for #<RSpec::ExampleGroups::Vote::AfterSave:0x007f9416c791e0>
# ./spec/models/vote_spec.rb:22:in `block (3 levels) in <top (required)>'
Finished in 0.28533 seconds (files took 2.55 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/models/vote_spec.rb:21 # Vote after_save calls `Post#update_rank` after save
Here is my vote_spec code:
require 'rails_helper'
describe Vote do
describe "validations" do
describe "value validation" do
it "only allows -1 or 1 as values" do
up_vote = Vote.new(value: 1)
expect(up_vote.valid?).to eq(true)
down_vote = Vote.new(value: -1)
expect(down_vote.valid?).to eq(true)
invalid_vote = Vote.new(value: 2)
expect(invalid_vote.valid?).to eq(false)
end
end
end
describe 'after_save' do
it "calls `Post#update_rank` after save" do
post = associated_post
vote = Vote.new(value: 1, post: post)
expect(post).to receive(:update_rank)
vote.save
end
end
end
And here is my post_spec code:
require 'rails_helper'
describe Post do
describe "vote method" do
before do
user = User.create
topic = Topic.create
#post = associated_post
3.times { #post.votes.create(value: 1) }
2.times { #post.votes.create(value: -1) }
end
describe '#up_votes' do
it "counts the number of votes with value = 1" do
expect( #post.up_votes ).to eq(3)
end
end
describe '#down_votes' do
it "counts the number of votes with value = -1" do
expect( #post.down_votes ).to eq(2)
end
end
describe '#points' do
it "returns the sum of all down and up votes" do
expect( #post.points).to eq(1) # 3 - 2
end
end
end
describe '#create_vote' do
it "generates an up-vote when explicitly called" do
post = associated_post
expect(post.up_votes ).to eq(0)
post.create_vote
expect( post.up_votes).to eq(1)
end
end
end
def associated_post(options = {})
post_options = {
title: 'Post title',
body: 'Post bodies must be pretty long.',
topic: Topic.create(name: 'Topic name',description: 'the description of a topic must be long'),
user: authenticated_user
}.merge(options)
Post.create(post_options)
end
def authenticated_user(options = {})
user_options = { email: "email#{rand}#fake.com", password: 'password'}.merge(options)
user = User.new( user_options)
user.skip_confirmation!
user.save
user
end
I'm not sure if providing the Post and Vote models code is necessary.
Here is my Post model:
class Post < ActiveRecord::Base
has_many :votes, dependent: :destroy
has_many :comments, dependent: :destroy
belongs_to :user
belongs_to :topic
default_scope { order('rank DESC')}
validates :title, length: { minimum: 5 }, presence: true
validates :body, length: { minimum: 20 }, presence: true
validates :user, presence: true
validates :topic, presence: true
def up_votes
votes.where(value: 1).count
end
def down_votes
votes.where(value: -1).count
end
def points
votes.sum(:value)
end
def update_rank
age_in_days = ( created_at - Time.new(1970,1,1)) / (60 * 60 * 24)
new_rank = points + age_in_days
update_attribute(:rank, new_rank)
end
def create_vote
user.votes.create(value: 1, post: self)
# user.votes.create(value: 1, post: self)
# self.user.votes.create(value: 1, post: self)
# votes.create(value: 1, user: user)
# self.votes.create(value: 1, user: user)
# vote = Vote.create(value: 1, user: user, post: self)
# self.votes << vote
# save
end
end
and the Vote model:
class Vote < ActiveRecord::Base
belongs_to :post
belongs_to :user
validates :value, inclusion: { in: [-1, 1], message: "%{value} is not a valid vote."}
after_save :update_post
def update_post
post.update_rank
end
end
It seems like in the spec vote model, the method assosicated_post can't be retrieved from the post spec model?
You're absolutely right - because you defined the associated post method inside of post_spec.rb, it can't be called from inside vote_spec.rb.
You have a couple options: you can copy your associated post method and put it inside vote_spec.rb, or you can create a spec helper file where you define associated_post once and include it in both vote_spec.rb and post_spec.rb. Hope that helps!