Agile Web Development with Rails 4 - Iteration E3 Unit Tests - ruby-on-rails

I'm new to Ruby on Rails and reading the book "Agile Web Development with Rails 4". As doing the "Playtime" exercises at the end of chapter 10 (Iteration E3 - Finishing the Cart), I stumbled across some problems.
One of it is in the 2nd exercise, where one should create unit tests to add unique and duplicate products to some shopping cart.
When one adds a product to that cart, it might be the first product of that kind and so the amount is one, but every additional add-operation increases the quantity. This works fine in browser-testing, but my test-cases fail.
Testcase:
test 'duplicates must not be saved as a new line item' do
# create cart and add one product
cart = new_cart_with_one_product(:ruby)
assert cart.save
assert_equal 1, cart.line_items.count
assert_equal 1, cart.line_items.find_by(
product_id: products(:ruby).id).quantity
assert_equal 49.50, cart.total_price.to_f
# ----------------------------------------------------------------
# create a second (actually the same product) and add it to cart:
item = products(:ruby)
cart.add_product(item.id, item.price)
assert cart.save
assert_equal 1, cart.line_items.count, 'duplicate saved as new line'
# test FAILS at the next two lines:
assert_equal 2, cart.line_items.find_by(product_id: item.id).quantity,
'quantity has not been increased'
assert_equal 99.00, cart.total_price.to_f, 'total price is wrong'
end
It tells me that the expected value is 2, but the actual value is 1.
So the quantity has not been increased. The total price does not change either, though both things work in the development-environment.
Here is the code of the Cart-Model:
class Cart < ActiveRecord::Base
has_many:line_items, dependent: :destroy
def add_product(product_id, product_price)
current_item = line_items.find_by(product_id: product_id)
if current_item
current_item.quantity +=1
else
# create a new line_item
current_item = line_items.build(product_id: product_id,
price: product_price)
end
current_item
end
def total_price
line_items.to_a.sum {|item| item.total_price }
end
end
I am using Rails 4.2.5 on Ruby 2.2.3.
I hope somebody can help me with that, because I do not understand why this is happening in the test-environment and using rake test only. If you need any additional code, please let me know.

I finally found my mistake:
The quantity is incremented in the model, for sure, but it isn't saved there.
It is saved in the controller, so this step is totally bypassed in my test because it just tests the model itself.
To fix the test, I changed cart.add_product(item.id, item.price)to cart.add_product(item.id, item.price).save to fix that issue.
I also reloaded the cart before testing, because otherwise it calculates the old total price (thanks to #PrakashMurthy though it should solve another issue, but at the end it helped :-) ).
The (working) Testcase looks like this, now:
test 'duplicates must not be saved as a new line item' do
# create cart and add one product
cart = new_cart_with_one_product(:ruby)
assert cart.save
assert_equal 1, cart.line_items.count
assert_equal 1, cart.line_items.find_by(
product_id: products(:ruby).id).quantity
assert_equal 49.50, cart.total_price.to_f
# ----------------------------------------------------------------
# create a second (actually the same product) and add it to cart:
item = products(:ruby)
# the following two lines do the trick:
cart.add_product(item.id, item.price).save
cart.reload
assert cart.save
assert_equal 1, cart.line_items.count, 'duplicate saved as new line'
assert_equal 2, cart.line_items.find_by(product_id: item.id).quantity,
'quantity has not been increased'
assert_equal 99.00, cart.total_price.to_f, 'total price is wrong'
end

Related

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

Fail Rspec Test Case + Integration Testing

test "TITLE" do
products = Product.all
total_price = products.to_a.sum(&:price)
expected_total = total_price * 100
post product_path, price: 500, product: "random"
assert_template :index
assert_equal expected_total, products.to_a.map(&:reload).sum(&:price)
end
I'm submitting my product randomly and it's working fine. but the price is not display after reload the products. It's display as 0. When I print the value in the form then it's display value in the price field.
On the first line, you don't have any products yet, so Product.all will return an empty collection. Therefore expected_total will be zero.
If you move this this setup code to after the post call then it should pass.
(However, your test is not actually verifying the implementation of the calculation).

Agile Development with Rails 4, Chapter 10 - playtime section: Misunderstood code

I am having a problem understanding a line of code in a suggested solution in the playtime section of Chapter 10 in Agile Development with Rails 4.
For an exercise, I am supposed to create a unit test that adds duplicate products.
The logic of the unit test is as follows: In the unit test, I should create a cart, add two of the same item (already declared as a fixture) to the cart, save the items then make assertions for what I expect the number of items and the total price should be. Below is the suggested solution. The problem is I don't understand this line nor its function: "assert_equal 2, cart.line_items[0].quantity". See the full test below. Thanks in advance!
test "add duplicate product" do
cart = Cart.create
ruby_book = products(:ruby)
cart.add_product(ruby_book.id).save!
cart.add_product(ruby_book.id).save!
assert_equal 2*book_one.price, cart.total_price
assert_equal 1, cart.line_items.size
assert_equal 2, cart.line_items[0].quantity
end
I commented your code:
test "add duplicate product" do
cart = Cart.create
ruby_book = products(:ruby)
cart.add_product(ruby_book.id).save!
cart.add_product(ruby_book.id).save!
# Checking that the total price of the cart equals the price of the book * the number of books.
assert_equal 2 * book_one.price, cart.total_price
# Only one item in the cart.
assert_equal 1, cart.line_items.size
# Only one item in the cart, but the quantity of the item is 2, since we added the same book twice.
assert_equal 2, cart.line_items[0].quantity
end

Why do i need to reload this CollectionProxy in the rails console but not when testing?

I've been creating a cart feature in rails and I have the following models:
Cart:
class Cart < ActiveRecord::Base
has_many :items
end
Item:
class Item < ActiveRecord::Base
belongs_to :cart
belongs_to :product
end
An item also has a quantity attribute.
Now, I have an instance method on cart that given an item will either a) save the item to the database and associate it with the cart or b) if the item with the product_id already exists, simply update the quantity.
The code for this is below:
def add_item(item)
if(item.valid?)
current_item = self.items.find_by(product_id: item.product.id)
if(current_item)
current_item.update(quantity: current_item.quantity += item.quantity.to_i)
else
self.items << item
end
self.save
end
end
And this works fine.
However, I wanted to test this in the console so i opened up the console in sandbox mode and ran the following commands:
cart = Cart.new #create a cart
cart.add_item(Item.new(product: Product.find(1), quantity: 5)) #Add 5 x Product 1
cart.items #lists items, I can see 5 x Product 1 at this point.
cart.add_item(Item.new(product: Product.find(1), quantity: 3)) #Add 3 x Product 1
cart.items #lists items, still shows 5 x Product 1, not 8x
cart.items.reload #reload collectionproxy
cart.items #lists items, now with 8x Product 1
Here i create a cart, add a purchase of 5 x Product 1 and i can see this in the cart.items. If then add another purchase of 3 x Product 1, the cart.items still lists the purchase as 5 x Product 1 until i manually reload the collection proxy.
I can add more products and these will show up, it is just when updating an existing one, it does not update the collection.
I have tests around this method too which pass.
before(:each) do
#cart = create(:cart)
#product = create(:product)
#item = build(:item, product: #product, quantity: 2)
end
context "when the product already exists in the cart" do
before(:each) {#cart.add_item(#item)}
it "does not add another item to the database" do
expect{#cart.add_item(#item)}.not_to change{Item.count}
end
it "does not add another item to the cart" do
expect{#cart.add_item(#item)}.not_to change{#cart.items.count}
end
it "updates the quantity of the existing item" do
#cart.add_item(#item)
expect(#cart.items.first.quantity).to eq 4
end
end
context "when a valid item is given" do
it "adds the item to the database" do
expect{#cart.add_item(#item)}.to change{CartItem.count}.by(1)
end
it "adds the item to the cart" do
expect{#cart.add_item(#item)}.to change{#cart.items.size}.by(1)
end
end
What i want to know is, why do i have to reload the CollectionProxy when I use this method in the console?
Association caches the results of the query to achieve better performance. When you call #cart.item for the first time it will call the db to get all the items associated with given cart and it will remember its output (in internal variable called 'target'), hence every time you call it after this initial call it will give you same results without calling db at all. The only way to force it to go again to db is to clear that target variable - this can be done with reload method or passing true to association call #car.items(true).
The reason why you don't need to reload association in you rspec tests is because you are not calling items on any object twice. However if you write test like:
it 'adds an item if it is not in the cart' do
before_count = #cart.items.size # size forces the association db call
#cart.add build(:item)
after_count = #cart.items.size # items has been fetched from db, so it will return same set of results
after_count.should_not eq before_count
end
That test will fail, since you are calling items twice on the same object - and hence you will get same results. Note, that using count instead of size will make this test to pass, because count is altering the SQL query itself(which results are not being cached) while size is being delegated to the association target object.

rspec controller testing after create action has completed

I'm having trouble testing this code in rspec - based on the error the test gives me, I know the test is written (more or less) correctly - as the data it's expecting is correct, it's just not getting it for some reason. I should also note that the code works in the browser.
edit: apologies if this was unclear. In this controller (evaluations_controller), the user iterates through each student in a given group and evaluates their progress against a set of goals. In the new action, #student = groups.student.first - when evaluation data for that student has been saved successfully in the create action, the student_id incremented by 1, and the new student_id is passed to the new action again (so the next student can be evaluated) - this loops until there are no more students.
What I'm trying to test is that the student_id is being successfully incremented after evaluation has been saved in the create action.
Code:
def create
...
if #evaluation.save
#id = params[:student_id]
#id = #id.to_i + 1
redirect_to evaluate_path({ student_group_id: #student_group, student_id: #id})
else
...
end
end
Rspec test:
it "should load the next student" do
#set #id to current student.id +1
#id = #student.id
#id = #id.to_i + 1
#post :create
post :create, {student_group_id: #student_group, student_id: #student, evaluation: #attr}
controller.params[:student_id].should eql #id
end
Error:
Failure/Error: controller.params[:student_id].should eql #id expected: 2 got: "1"
Your code appears to be flawed, and consequently your test is not clear.
From gleaning over the code, I understand you want to use some type of next/previous student functionality. It appears you are hacking around your controller test to achieve that.
if #evaluation.save
#id = params[:student_id]
#id = #id.to_i + 1
You are manually calculating the next id. Ask yourself this: What happens if you are working with student.id 1, and you run this calculation, but student.id 2 has been deleted?
You get an ActiveRecord error.
You need a better way of pulling the next student. You should add an instance method to your Student model to handle that for you:
def next
Student.where(id: id).order("id ASC").first
end
In your controller you can move to the next student as such:
redirect_to evaluate_path({ student_group_id: #student_group, student_id: #student.next.id})
Then your test should be much simpler.

Resources