Testing my models method in rails - ruby-on-rails

I have to test the creation of my account. This is my Account.create() in controllers method.
Account.create( account: { name: "#{params[:account][:name]}",
description: "#{params[:description]}" }, user: { email: current_user.name })
I have to test my model method Account.create().
post :create, '/account'
Below is my spec class.
require 'spec_helper'
describe Account do
before(:all) do
end
it "can create an account" do
FactoryGirl.create :account, name: "something", description: "something"
# Account.create()
end
end
I am not sure on how to procced.

FactoryGirl is a tool to use when the methods you want to test need a complicated predefined state. When account creation has no dependencies you could simply do something like:
it "can create an account" do
acc = Account.create(name: 'something', description:'something')
acc.name.should == 'something'
acc.description.should == 'something'
end

Please, take a look at this article, it explains how to test Models.
I your case it should be like this:
it "can create an account" do
FactoryGirl.create(:account, name: "something", description: "something").should be_valid
end

Related

How do I unit test the current_user RoR

I have this method to check if the user is admin:
def admin?
current_user.admin == true
end
The unit test is:
require 'rails_helper'
describe StubController do
describe '.admin?' do
it "should tell if the user is admin" do
user = User.create!(email: "i#i.com", password:'123456', role: "admin", name: "Italo Fasanelli")
result = user.admin?
expect(result).to eq true
end
end
end
The problem is, simplecov is telling me that this part current_user.admin == true is not covered.
How do I test the current_user in this test?
First off, move the admin? method to User model so that it can be reused across Model-View-Controller.
class User < ApplicationRecord
def admin?
role == 'admin'
end
end
You can use this method wherever you have access to the instance of User. So current_user.admin? would also work across views and controller.
Now you should write test for model not the controller. Also I noticed that you create user model object manually instead of using Factory. Use FactoryBot to create required instances for testing.
Here is a quick spec assuming there is factory is set for user
require 'rails_helper'
RSpec.describe User, type: :model do
describe '.admin?' do
context 'user has role set as admin' do
let!(:user) { build(:user, role: 'admin') }
it 'returns true' do
expect(user).to be_admin
end
end
context 'user has role set as non admin' do
let!(:user) { build(:user, role: 'teacher') }
it 'returns true' do
expect(user).not_to be_admin
end
end
end
end

Run before block once with RSpec 3

I can't find a way around this.
This is my test:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
let(:post_params) do
{
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
end
before do
post :create, params: post_params
end
context 'successful create' do
subject(:user) { User.find_by_email(post_params[:email_address]) }
it 'persists the user' do
expect(user).not_to be_nil
end
it 'user data is correct' do
post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
it 'returns responsde code of 201' do
expect(response.status).to eq(201)
end
end
end
end
I only want this controller to be hit once. However, I can't seem to get that to work.
I have tried setting before(:context) and I get an error
RuntimeError:
let declaration `post_params` accessed in a `before(:context)` hook at:
`let` and `subject` declarations are not intended to be called
in a `before(:context)` hook, as they exist to define state that
is reset between each example, while `before(:context)` exists to
define state that is shared across examples in an example group.
I don't want multiple users to be persisted for such a simple test. I also dont want to be hitting the api for every example.
I want the before block to run once. How can I do this?
As the error message states, let and subject are specifically for managing per-example state. But before(:context)/before(:all) hooks get run outside the scope of any specific example, so they are fundamentally incompatible. If you want to use before(:context), you can't reference any let definitions from the hook. You'll have to manage the post_params state yourself without using let. Here's a simple way to do that:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
before(:context) do
#post_params = {
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
post :create, params: #post_params
end
context 'successful create' do
subject(:user) { User.find_by_email(#post_params[:email_address]) }
it 'persists the user' do
expect(user).not_to be_nil
end
it 'user data is correct' do
#post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
it 'returns responsde code of 201' do
expect(response.status).to eq(201)
end
end
end
end
That should solve your problem; however it's not the approach I would recommend. Instead, I recommend you use the aggregate_failures feature of RSpec 3.3+ and put all of this in a single example, like so:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
let(:post_params) do
{
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
end
it 'successfully creates a user with the requested params', :aggregate_failures do
post :create, params: post_params
expect(response.status).to eq(201)
user = User.find_by_email(post_params[:email_address])
expect(user).not_to be_nil
post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
end
end
aggregate_failures gives you a failure report indicating each expectation that failed (rather than just the first one like normal), just like if you had separated it into 3 separate examples, while allowing you to actually make it a single example. This allows you to incapsulate the action you are testing in a single example, allowing you to only perform the action once like you want. In a lot of ways, this fits better with the per-example state sandboxing provided by RSpec's features like before hooks, let declarations and the DB-transaction rollback provided by rspec-rails, anyway. And
I like the aggregate_failures feature so much that I tend to configure RSpec to automatically apply it to every example in spec_helper.rb:
RSpec.configure do |c|
c.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
end
What you are looking for is before(:all), which will run once before all the cases.
There is similar after(:all) as well.
Interestingly, the before is basically a shorter way of saying before(:each) (which IMO makes more sense).

Where to create parent model instances in feature test (Capybara)?

I am testing the creation of a product with Capybara, i.e., I am filling a form with automated test. This product belongs to certain models, for example, to a home.
I have two factory files to create this two models, the product and the house. In the form, user should select the home from a select (Drop down). I manage to do it, but the solution feels not clean:
(I am creating the home instance in the feature test, since I need a home to be selected in the form for the product. This house belongs to other models)
require 'rails_helper'
require 'pry'
RSpec.describe 'Add a product features' do
context "Create new product from add a product menu" do
let(:user) { create(:user) }
let!(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
# home.name = "My place"
# home.save
end
before(:each) do
# binding.pry
login_as(user, :scope => :user)
visit menu_add_product_path
click_link("Take a picture")
expect(current_path).to eql('/products/new')
binding.pry
within('form') do
attach_file('product_taken_photos_attributes_0_taken_pic', File.absolute_path('./app/assets/images/macbook1.jpg'))
fill_in 'Brand', with: "Apple"
fill_in 'Product type', with: "Smartphone"
fill_in 'Price of purchase', with: 800.3
fill_in 'Date of purchase', with: "2017-05-03"
select("My place", :from => 'product_home_id')
end
end
it 'should be successful' do
binding.pry
within('form') do
fill_in 'Model', with: "Iphone 6"
end
click_button('Create Product')
binding.pry
expect(current_path).to eql(product_path(Product.last))
expect(page).to have_content 'Iphone 6'
end
# it 'should not be successful' do
# click_button('Create Product')
# expect(current_path).to eql('/products') # the post products actually!
# expect(page).to have_content(/Model can\'t be blank/)
# end
end
end
Factories:
home.rb
FactoryBot.define do
factory :home do
sequence(:name) { |n| "My Home#{n}" }
address 'c/ Viladomat n200 50 1a'
insurer
house_type
user
end
end
product.rb
FactoryBot.define do
factory :product do
model 'macbook pro'
form_brand 'apple'
form_product_type 'laptop'
price_of_purchase 1200
date_of_purchase Date.new(2017,06,06)
end
end
user.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "myemail#{n}#mail.com" }
password 123456
end
end
house_type.rb
FactoryBot.define do
factory :house_type do
name 'Flat'
end
end
If I use the let! operator to create a home for all the tests, the test fails:
let!(:home) { create(:home, name: "My Place", user: user) }
Console log:
Capybara::ElementNotFound:
Unable to find visible option "My place" within #<Capybara::Node::Element tag="select" path="/html/body/div[2]/form/div[4]/div/div[2]/select">
But, if I create the home manually, before each test, it works
let(:home) { create(:home, name: "My Place", user: user) }
before(:each) do
home.name = "My place"
home.save
end
Why is the let! not working? If I put a binding.pry in my test, in both cases I have the created home in my database.
You should be configuring your factories to automatically create needed default associations so you can create a needed instance in your tests without having to create all the other non-specialized records. Your home factory should look something like
FactoryBot.define do
factory :home do
sequence(:name) { |n| "Home#{n}" }
address { 'c/ Viladomat n200 50 1a' } # You might want to define this to use a sequence too so it's unique when you create multiples
insurer
house_type
user
end
end
Something like that would then let you create a valid Home instance by just calling create(:home). If you want to customize any of associations/parameters you can pass them to the factory create/build method. So in your example it would just become
let(:home) { create(:home, name: 'My place') }
If you wanted to also manually create the user object, so you can call login(user...) rather than having to access an auto generated user like login(home.user...) then you would do
let(:user) { create(:user) }
let!(:home) { create(:home, name: 'My place', user: user }
Note the use of let! for home rather than let. This is because let is lazily evaluated so the instance won't actually be built until you first call home in your test - Since, when calling login_as(user..., you don't call home in your test you need to use let! instead so the object is created before your test is run. You also probably want to be using FactoryBot sequences in things like the email of your user factory, so that you can create more than one user in tests.
Additionally you're calling expect(current_path).to eql('/new_from_camera'), which will lead to flaky tests since the eql matcher doesn't have waiting behavior built-in. Instead you should always prefer the Capybara provided matchers which would mean calling expect(page).to have_current_path('/new_form_camera') instead.
I think you can add the associations directly in the home factory:
let(:insurer) { create(:insurer) }
let(:house_type) { create(:house_type) }
let(:user) { create(:user) }
let(:home) { create(:home, name: "My place", insurer: insurer, house_type: house_type, user: user) }

`method_missing': `build` is not available on an example group (e.g. a `describe` or `context` block)

I wanted to remove the FactoryGirl.build(:user) everytime I want to create a user and so I added these lines:
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
to the spec_helper.rb. But this creates the following error:
`method_missing': `build` is not available on an example group (e.g. a `describe` or `context` block). It is only available from within individual examples (e.g. `it` blocks) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). (RSpec::Core::ExampleGroup::WrongScopeError)
Then I removed all the context/describe blocks, but that didn't change anything. Have any of you had the same problem with this and how may I fix it?
Currently my tests look like so:
require 'rails_helper'
RSpec.describe User, type: :model do
user = build(:user)
project = build(:project)
it "is valid with a firstname, lastname, email and password" do
expect(user).to be_valid
end
it "is invalid without a firstname" do
user = build(:user, name: nil)
expect(user.valid?).to be_falsey
expect(user.errors[:name].size).to eq(1)
end
it "is invalid without a lastname" do
user = build(:user, surname: nil)
expect(user.valid?).to be_falsey
expect(user.errors[:surname].size).to eq(1)
end
it "destroys dependent projects" do
user = User.create!(name: 'john', surname: 'doe', email: 't#example.com', password: 'password', password_confirmation: 'password')
user.projects << project
expect{user.destroy}.to change {Project.count}.by(-1)
end
end
Instead of:
user = build(:user)
project = build(:project)
Do:
let(:user) { build(:user) }
let(:project) { build(:project) }
In general it is not a good idea to define external variables to use them in a test, as this might make your tests order-dependent and extremely hard to debug. Always use the let syntax, so the values are reinitialized for every test.

FactoryGirl creating multiple records

I'm trying to get in the habit of writing specs, however, this is becoming increasingly frustrating.
Assume I have two simple models: User and Story. Each model uses a belongs_to relation. Each model uses a validates :foo_id, presence: true as well.
However, FactoryGirl is creating multiple records.
FactoryGirl.define do
factory :user do
email "foo#bar.com"
password "foobarfoobar"
end # this creates user_id: 1
factory :story do
title "this is the title"
body "this is the body"
user # this creates user_id: 2
end
end
This simple test fails:
require 'rails_helper'
describe Story do
let(:user) { FactoryGirl.create(:user) }
let(:story) { FactoryGirl.create(:story) }
it 'should belong to User' do
story.user = user
expect(story.user).to eq(user)
end
end
What am I missing here? I cannot build a Story factory without a User, yet I need it to be just one User record.
The values you define for each attribute in a factory are only used if you don't specify a value in your create or build call.
user = FactoryGirl.create(:user)
story = FactoryGirl.create(:story, user: user)
When doing something like this you can do:
let(:story) { FactoryGirl.create(:story, user: user) }
Or maybe you can only let the story variable and do:
let(:story) { FactoryGirl.create(:story, user: user) }
let(:user) { User.last}
Yes, it is a feature of factory girl to create the associated user when you create the story.
You can avoid it like this:
require 'rails_helper'
describe Story do
let(:story) { FactoryGirl.create(:story) }
let(:user) { story.user }
it 'should belong to User' do
story.user.should eq user
end
end
This example is setup to be trivially true, but you get the point.

Resources