Rspec testing for duplicates? - ruby-on-rails

I am trying to test for "A product can list users that have reviewed it without duplicates"
this is what my test looks like
product_spec.rb
describe Product do
let!(:product) { Product.create }
.
.#other test
.
it "can list users that review it without duplicates" do
product.reviews.create({user_id: 1, review: "test"})
product.reviews.create({user_id: 1, review: "test2"})
product.user.uniq.count.should eq(1)
end
end
terminal result
1) Product can list users that review it without duplicates
Failure/Error: product.reviews.create({user_id: 1, review: "test"})
ActiveRecord::RecordNotSaved:
You cannot call create unless the parent is saved
# ./spec/models/product_spec.rb:49:in `block (2 levels) in <top (required)>'

The problem is in this line:
product.save.reviews.create
Save returns boolean whether object has been saved successfully or not. You need to split this into two lines:
product.save
product.reviews.create

You are trying to create a Review on a Product that hasn't been saved with:
product.reviews.create()
I'm guessing product isn't valid, thus it hasn't been saved by
let!(:product) { Product.create }
which simply returns the invalid object if create fails.
You should
Use create! to make sure you notice when saving the object fails (it'll raise an exception if there's a validation error).
Make sure your Product can be created with the data, you provide it.

Related

the right way to change the associated object in rspec

I recently started to test with rspec, so I can strongly be mistaken, correct me if there is a better way
I create two related models
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user}
and before one of the tests change one of the related objects
context "when" do
before {participation.prize = 100}
it "" do
binding.pry
end
end
But inside it
participation.prize => 100
user.participatons.select(:prize) => nil
what am I doing wrong ? and how to fix it?
When you say user.participations.select(:prize), you're making a query to the db to get values in the user's participations' prize columns. But when you say before {participation.prize = 100} you're only setting the prize attribute on the participation object. Try saving the participation before the select line:
participation.prize # => 100
participation.save
user.participatons.select(:prize) # => nil
Another possible issue is that user.participations has been memoized by a previous call. Ensure that user.participations.first == participation. If it doesn't, check
1) puts participation.user_id and
2) puts user.participations, user.reload.participations
Lastly, a better way of setting up the test so that you run into this issue less often is something along the lines of:
# let(:price) { 0 } # default price. Optional so that tests won't throw errors if you forget to set it in a context/describe block.
let(:user) {FactoryGirl.create :user}
let!(:participation) {FactoryGirl.create :participation, user: user, price: price}
# ...
context "when ..." do
let(:price) { 100 }
it "" do
binding.pry
end
end
This way, the price is set when you create the model. Following this pattern generally means running into this problem less.

Fixture reverts value after controller method

My app for teachers includes a method to delete a student from a seminar (a class but I didn't want to use the word "class") by deleting that student's record from the join table. I don't delete the student directly, but I delete an "Aula," which is a record in the student/seminar join table. I'm trying to write a test to ensure that when a student is deleted, that student's id is also deleted from the seating chart array. This function appears to be working correctly in development, but not in testing.
The Test
test "Remove student from class period and seating chart" do
log_in_as(#teacher_user)
get seminar_path(#seminar)
seating = [#student.id, #other_student.id]
#seminar.update(:seating => seating)
assert_difference ['Aula.count','#student.aulas.count', '#seminar.students.count'], -1 do
delete aula_path(#aula)
end
puts "In test"
puts #seminar.seating
assert_redirected_to scoresheet_url(#seminar)
end
The Destroy Method in the Aula Controller
def destroy
thisAula = Aula.find(params[:id])
#seminar = Seminar.find(thisAula.seminar_id)
#Remove student from seating chart
seating = #seminar.seating
seating.delete(thisAula.student_id)
#seminar.update(:seating => seating)
puts "In Controller"
puts #seminar.seating
thisAula.destroy
#Redirect
flash[:success] = "Student removed from class period"
redirect_to scoresheet_url(#seminar)
end
Once I fix this error, I would like to include the seating chart count within the test, like so:
assert_difference ['Aula.count','#student.aulas.count', '#seminar.students.count', '#seminar.seating.count'], -1 do
But the test fails there, because the seating array is not decreasing its count.
I've tried those "puts" lines to figure out what is happening.
Thank you in advance for any insight.
-Jeff
Running the test puts:
In Controller
614857506
In Test
45061424
614857506
This shows that the student's id is deleted from the seating array like it should be. But when the program returns to the test, the seating array has reverted to its original form, which includes the id of the deleted student.
Got it. It was a case of needing to reload the model.
#seminar.reload

failure with creating an instance in specs

I have a model Ticket which has department_id, and Department with
enum name: { dept1: 0, dept2: 1, dept3: 2 }
I have seeded db with these three departments
Department.create(name: :dept1)
Department.create(name: :dept2)
Department.create(name: :dept3)
So I try to write specs on Ticket method
def dept
self.department.name.humanize
end
here is an example
describe '.dept' do
let!(:ticket){ create :ticket, department_id: Department.first.id }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
And I recieve an error
ActiveRecord::RecordInvalid:
Validation failed: Department can't be blank
I'm a new guy to rails, so please i9f you don't mind explain me how to write such specs( with seeded db). Any advises would be very useful for me. Thanks!
You'll want to refrain from seeding your database and instead create records that you need for each test.
describe '#dept' do
let(:department) { create :department, title: 'dept1' }
let(:ticket) { build :ticket, department: department }
it 'should return right dept' do
expect(ticket.dept).to eq 'Dept1'
end
end
Notice that I also changed ticket so it's generated by build instead of create. Based on what I see, it doesn't look like you need the overhead of persisting ticket to the database in order to run this particular test.
Also, another small point... But the "convention" (if such a thing exists) is to describe instance methods with hashes in front of them instead of a dot. (Dot denotes a class method.)

How to delete an entire array in Ruby and test with RSpec

I'm fairly new to Ruby and am currently taking a full stack course. For one of my projects we are building an addressbook. I have set up how to add an entry to the addressbook, however, I can't seem to figure out how to delete an entry (I make an attempt with the remove_entry method in the AddressBook class below but am not having any luck). We are also supposed to test first with RSpec, have the test fail and then write some code to get it to pass. If I didn't include all the info needed for this question let me know (rookie here). Anyway, here is what I have so far:
RSpec
context ".remove_entry" do
it "removes only one entry from the address book" do
book = AddressBook.new
entry = book.add_entry('Ada Lovelace', '010.012.1815', 'augusta.king#lovelace.com')
book.remove_entry(entry)
expect(entry).to eq nil
end
end
AddressBook class
require_relative "entry.rb"
class AddressBook
attr_accessor :entries
def initialize
#entries = []
end
def add_entry(name, phone, email)
index = 0
#entries.each do |entry|
if name < entry.name
break
end
index += 1
end
#entries.insert(index, Entry.new(name, phone, email))
end
def remove_entry(entry)
#entries.delete(entry)
end
end
Entry class
class Entry
attr_accessor :name, :phone_number, :email
def initialize(name, phone_number, email)
#name = name
#phone_number = phone_number
#email = email
end
def to_s
"Name: #{#name}\nPhone Number: #{#phone_number}\nEmail: #{#email}"
end
end
When testing my code with RSpec I receive the following error message:
.....F
Failures:
1) AddressBook.remove_entry removes only one entry from the address book
Failure/Error: expect(entry).to eq nil
expected: nil
got: [#<Entry:0x00000101bc82f0 #name="Ada Lovelace", #phone_number="010.012.1815", #email="augusta.king#lovelace.com">]
(compared using ==)
# ./spec/address_book_spec.rb:49:in `block (3 levels) in <top (required)>'
Finished in 0.02075 seconds (files took 0.14221 seconds to load)
6 examples, 1 failure
Failed examples:
rspec ./spec/address_book_spec.rb:44 # AddressBook.remove_entry removes only one entry from the address book
Just test that the book.entries association is empty:
expect(book.entries).to be_empty
As book is a local variable in your test, you will not get a false negative result if you keep your test atomic. Some best practices on rspec.
Edit:
You can also check the entry was not in the set:
expect(book.entries.index(entry)).to be_nil
or test the change of the array length with:
expect { book.remove_entry(entry) }.to change{book.entries.count}.by(-1)
If you wonder for the be_xxx syntax sugar, if the object respond to xxx?, then you can use be_xxx in your tests (predicate matchers)
I think your expect has an issue. The entry variable is not set to nil, but the entry inside book would be nil.
I think something like this would work better:
expect(book.entries.find { |e| e.name == "Ada Lovelace" }).to eq nil
Better still, your AddressBook could have its own find method, which would make the expect param much nicer, like book.find(:name => "Ada Lovelace").
Finally, I would also put an expect call before the remove_entry call, to make sure its result equals entry.

Asserting the last record in table

I'm trying to assert that the last record did not get deleted in rails model unit test. I raise an exception if the record.count.one? is true. Initially there are two records.
Edited:
There is a user story that says you can delete the users.
You cannot delete the user that you are logged in with. (functional test)
You cannot delete the last user. (unit test)
here it is:
test "verify cannot destroy last user" do
assert_raise(RuntimeError) {
User.find(:all).select {|u| u.destroy} }
assert_equal 1, User.count
end
Here's my literal translation of what you are asking (I think):
last_user = User.last
...
assert_equal last_user, User.last
Here's more traditional test code that is a bit less fragile:
assert_difference('User.count',-1) do
...
end
(But Gutzofter may actually be onto what you're looking for.)

Resources