25 Mar 2015
Interact with Emails in Feature Tests:
Sometimes applications may require you to complete a form and an email is
is generated.
To test this feature, Capybara-email is a great way to test this functionality and provides a nice interface that allows you to write specs on the level of user interaction. This is better than as creating a feature test for ActionMailer and its deliveries because it will require too much low level detail.
gem 'capybara-email'
documentation: github//dockyard/capybara-email
Example spec for inviting a user:
#user_resets_password_spec.rb
require 'spec_helper'
feature 'User resets password' do
scenario 'user successfully resets the password' do
alice = Fabricate(:user, password: 'old_password')
visit sign_in_path
click_link "Forgot Password"
fill_in "Email Address", with: alice.email
click_button "Send Email"
open_email(alice.email)
current_email.click_link("Reset My Password")
fill_in "New Password", with: "new_password"
click_button "Reset Password"
fill_in "Email Address", with: alice.email
fill_in "Password", with: "new_password"
click_button "Sign in"
expect(page). to have_content("Welcome, #{alice.full_name}")
clear_email
end
end
20 Mar 2015
Testing Styles in Rails: Three Styles of Behavior Driven Development
Traditionally we have followed the following flow:
Meet in the Middle Technique
- Turn mock ups into view templates:
copy the mockups and replace the placeholder data
- Controller Tests (inner layer)- the controller tests will drive the model layer
tests, routes, db migrations, etc.
- Model Level Tests(inner layer) - if we need active record based and non-active record based
model - logic, we will need to drop down to model layer to test their
- Feature Specs/Integration - (Outer most layer) Tests will allow us to test everything including the view templates
- Here you will end up with less feature/integration specs than outside in because they are typically written after multiple specs.
Inside out Approach
- Start from the models and migrations that are required for that feature and then
right specs to test that model
- Controller Level
- View/Intergation Specs
- easier to start .. allows you to work with something small
- But you sometimes don't know where you're going and you may end up
adding alot of code that is not needed.
- This is used primarily in productized shops where its easier to add a model to a more mature product.
Outside in Approach
Start at the Integration Level - This integration level does not mean in the
same way that we reference the Integration Tests in the "Meet in the Middle" technique where where we test multiple features (horizontal) but rather vertical -integration means where we test for models, controllers and views for one feature
- written in a very business level decision level - you have to think 'what is the business value we are looking to provide?'
- route, controller, models
- also called BDD or ABDD (A is for Acceptance)
- often used in consultancys, clients provide the feature
- often follows more a natural flow
- requires you to know rails really well and how things work together
- people often use cucumber or rspec feature tests for this.
17 Mar 2015
The[ previous example reegarding updating video review while in queue does not follow typical Rails convention:
- We are using in the updatequeue method.
- we have to use parameter naming conventions to parse out the queueitem id and
position id in the form.
- We have to specify the order of queue_items using in the user model according to position.
Using Transactions & Exception Rescues when the value does not save.
def update_queue
begin
ActiveRecord::Base.transaction do
params[:queue_items].each do |queue_item_data|
queue_item = QueueItem.find(queue_item_data["id"])
queue_item.update_attributes!(position: queue_item_data["position"])
end
end
rescue ActiveRecord::RecordInvalid
flash[:error] = "You must use whole numbers for play position."
redirect_to my_queue_path
return
end
current_user.queue_items.each_with_index do |queue_item, index|
queue_item.update_attributes(position: index+1)
end
redirect_to my_queue_path
end
15 Mar 2015
Parameter Naming Conventions
Visit Rails Guides' Parameter Naming Conventions
Use that as the starting point to construct the params that hit the server.
As you've seen in the previous sections, values from forms can be at the top level of
the params hash or nested in another hash. For example, in a standard create action for a
Person model, params[:person] would usually be a hash of all the attributes for the person to create.
The params hash can also contain arrays, arrays of hashes and so on.
Fundamentally HTML forms don't know about any sort of structured data, all they generate
is name-value pairs, where pairs are just plain strings. The arrays and hashes you see
in your application are the result of some parameter naming conventions that Rails uses.
Basic Structures
The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in params. For example, if a form contains:
<input id="person_name" name="person[name]" type="text" value="Henry"/>
the params hash will contain
{'person' => {'name' => 'Henry'}}
and params[:person][:name] will retrieve the submitted value in the controller.
Hashes can be nested as many levels as required, for example:
<input id="person_address_city" name="person[address][city]" type="text" value="New York"/>
will result in the params hash being
{'person' => {'address' => {'city' => 'New York'}}}
Normally Rails ignores duplicate parameter names. If the parameter name contains
an empty set of square brackets [] then they will be accumulated in an array.
If you wanted users to be able to input multiple phone numbers, you
could place this in the form:
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
This would result in params[:person][:phone_number] being an array containing the inputted phone numbers.
Combining Them
We can mix and match these two concepts. One element of a hash might be an array as in the previous example,
or you can have an array of hashes. For example, a form might let you create any number of addresses by repeating the following form fragment
<input name="addresses[][line1]" type="text"/>
<input name="addresses[][line2]" type="text"/>
<input name="addresses[][city]" type="text"/>
This would result in params[:addresses] being an array of hashes with keys line1, line2 and city.
Rails decides to start accumulating values in a new hash whenever it encounters an input name that
already exists in the current hash.
There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can usually be replaced by hashes; for example, instead of having an array of model objects, one can have a hash of model objects keyed by their id, an array index or some other parameter.
10 Mar 2015
In Myflix, we want to be able to allow the user to update video rating in the video_play queue.
Goal: To change video rating from the queueitem index.
To do so, we have to create the virtual attribute on the queueitem model, as though it is a column on the queue_item model (table). Since it is not in the model, we have to define the getter and setter.
#queue_item.rb
def rating
end
def rating=(new_rating)
-this is where you begin to write your tests
end
Update Column vs Update Attribute
updateattributes will go through validation error unlike the updatecolumn method,which will bypass validation.
def rating=(new_rating)
review = Review.where(user_id: user.id, video_id: video.id).first
review.update_column(:rating, new_rating)
end
Memoization
Memoization means the first time the record is called, it will hit the database only once and store that info
into the instance variable and will not database again when record is called.
def review
@review ||= Review.where(user_id: user.id, video_id: video.id).first
end