random data production in feature steps - ruby-on-rails

class Spinach::Features::Signup < Spinach::FeatureSteps
attr_accessor :valid_attributes
before do
valid_attributes = Fabricate.attributes_for(:identity)
##valid_attributes = Fabricate :identity
end
step 'I am a visitor' do
true
visit root_path
end
step 'I am on the landing page' do
current_path.must_equal root_path
end
step 'I follow signup link' do
click_link('signup_link')
end
step 'I fill name with my name' do
fill_in 'name', with: valid_attributes.name
end
step 'I fill email with my email' do
fill_in "email", with: valid_attributes.email
end
end
i use spinach gem for creating feature steps. above code id my feature steps. i also use minitest for testing framework. i use fabricator gem for creating random datas.
require "ffaker"
Fabricator(:identity) do
name {Faker::Name.name}
email {Faker::Internet.email}
password_digest "ChtUIGTiBvrm6v6R4PX6sO3netSuN3eW0AbFmXblXvgKM5Z8sFUKy"
end
this is my fabricator class for identity model. when i run signup feature, i see an error:
undefined method `name' for nil:NilClass
i think that it is about Fabricate.Attributes_for. if i use Fabricate :identity, it doesn't give error.
i couldn't solve this. Any ideas? Thanks in advance.

When you do:
valid_attributes = Fabricate.attributes_for(:identity)
You've got a Hash.
So do: valid_attributes[:email] or use an Openstruct.

Related

Factory bot and problems with loging in in RSpec

I've got a problem with Factory bot and logging in as a designated user. I'm trying to run a simple Edit test in rspec. Here it is:
require "rails_helper"
RSpec.describe "Treat management", :type => :system do
before do
treat = FactoryBot.create(:treat)
user = build(:user, email: 'wojtek#gmail.com', password: 'password')
login_as(user)
driven_by(:selenium_chrome_headless)
end
it "enables me to edit treats" do
visit root_path
click_button 'Edit'
fill_in 'Name', with: 'A new name'
fill_in 'Content', with: 'A new content'
click_button "Update Treat"
expect(page).to have_text("Treat was edited successfully")
end
end
And here is my Treat factory. Treats have a name, content and a giver and a receiver foreign keys
FactoryBot.define do
factory :treat do
name {'my first factory treat'}
content {'this is my first treat created by a factory'}
giver factory: :user
receiver factory: :user
end
end
And of course the user factory. Users are defined by email and password
FactoryBot.define do
factory :user do
email {Faker::Internet.email}
password {'password'}
end
end
And you have to know the edit buttom is only present when the logged user is also the giver. I have asked around and supposedly my Treat factory is is well configured. Please help me solve this. If any other parts of code are required please let me know in comments and I'll update accordingly. And of course I know that there is a simplier way to write this test but the use of the factories is a requirement.
1
I have tried hardcoding the user in the factory (without the Faker gem) but that trigers the validation error - the email has been taken.
Right now FactoryBot.create(:treat) will create a User for giver and User for receiver based on the Factory definition.
FactoryBot.define do
factory :treat do
name {'my first factory treat'}
content {'this is my first treat created by a factory'}
giver factory: :user # tells the factory to create a User from the User Factory
receiver factory: :user # tells the factory to create a User from the User Factory
end
end
You are calling this in your test but then creating a third user to test with
before do
treat = FactoryBot.create(:treat) # 2 users created
# changed to `create` since as #max pointed out `build` does not actually create a `User`
user = create(:user, email: 'wojtek#gmail.com', password: 'password') # third user
end
This third user is neither the giver or receiver of the Treat which is why your test fails.
Instead you can override definitions in the Factory by passing arguments to create. In this case you want the User object under test to be the giver of the Treat so we can achieve this as follows (I used modified version of #max's test scheme as it is the preferred way to set this up)
require "rails_helper"
RSpec.describe "Treat management", type: :system do
let(:user) { create(:user) }
before do
driven_by(:selenium_chrome_headless)
end
context 'A Treat#giver' do
let!(:treat) {create(:treat, giver: user)}
before do
login_as(user)
end
it "can edit Treats they've given" do
visit root_path
click_button 'Edit'
fill_in 'Name', with: 'A new name'
fill_in 'Content', with: 'A new content'
click_button "Update Treat"
expect(page).to have_text("Treat was edited successfully")
end
end
end
Here we replace the default creation of a "giver" user with the specific user returned by user method defined in the let block. This ensures that user == treat.giver so that your test can succeed.

Capybara with FactoryGirl?

So I want to make sure this is possible and actually doable with what im trying to do.
I had previously been using a seeds.rb file to seed the database with test data, this worked for my Capybara integration tests but messed up some of the other unit tests which rely on a schema.rb file and Factories to create data.
Can I use a factory to create test data for that particular integration spec. Something like this for a factory:
FactoryGirl.define do
factory :user do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
email Faker::Internet.email
password "password"
end
end
Then I have a super simple spec which logins to a site in Capybara:
it 'Check correct login' do
visit('/sign_in')
page.fill_in 'user_email', :with => 'test#test.com'
page.fill_in 'user_password', :with => 'p4ssw0rd'
click_button('Sign In')
end
This is hitting a Sqlite database, can I insert my factory and it will work using capybara? Im really not familiar with factorygirl, but is it actually creating data in the database and then just removing it?
I don't commonly see Capybara used with FactoryGirl in a lot of the examples I search online, is there a reason for that?
edit:
Here is what I have currently:
it 'Check correct login' do
user = FactoryGirl.create(:user)
visit('/d/users/sign_in')
page.fill_in 'user_email', :with => user.email
page.fill_in 'user_password', :with => user.password
click_button('Log In')
page.assert_text('Signed in successfully.')
end
I was able to take a screenshot and confirm that the forms are getting filled in with a random username/password, but using DB Browser for SQlite I don't seem to see the actual data in the database getting populated (Which might be difficult since once the spec is done it's deleted right?) but I can't tell if it's actually creating the data or not.
Thanks!
This should work fine w/o many hitches. You'll need to instantiate your User object in your test as this looks like a 'sign-in'.
You can create the object a couple of different ways.
(1)
describe 'Test' do
let(:user){ create :user }
...
it 'Specific Test' do
...
Note that let!(:user){...} creates immediately and let(:user){...} will only create user when the user is called. You will then be able to access user in all your tests.
(2)
describe 'Test' do
before do
#user = FactoryGirl.create( :user )
end
it 'Specific Test' do
...
Here you will be able to access #user in all your tests.
Let us know if that works and if not what error you're getting.
EDIT:
If your FG file looked like this:
FactoryGirl.define do
factory :user do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
email Faker::Internet.email
password "password"
work
end
end
You could associate work like this:
let(:user){ create :user, work: 'Some Place' }
#user = FactoryGirl.create(:user, work: 'Some Place')

How do I make a Capybara Rails TestCase run as a single transaction?

I have a rails app that includes a blog feature.
In one single feature test (using Capybara::Rails::TestCase, with ruby tests (asserts/refutes) i.e, not spec) for the blog, I want to test adding a post, adding comments, editing the post, etc. as individual tests - each of these tests builds upon the last one, as the post created in the first test is commented on in the second test, and so on.
I have seen posts which show workarounds for doing this in a unit test (global variables, use setup/teardown), but I wondered if there is a more direct way to do it in a feature test, since it is likely more common here.
Ideally, I want the login session to persist, as well as database records created in previous tests to persist across each test in the TestCase. Setup and teardown could be used to login each time, but not the intermediate records created for posts, comments, etc.
I want something like:
class BlogTest< Capybara::Rails::TestCase
test 'can sign in' do
user = User.create!(name: "user",
email: "user#example.com",
password: "passw0rd!", password_confirmation: "passw0rd!")
visit new_user_session_path
fill_in('Login', :with => user.email)
fill_in('Password', :with => user.password)
check('Remember me')
click_button('Sign in')
end
test 'can create post' do
visit new_post_path # how can I have user logged in?
fill_in "Title", with: "My first post title!"
fill_in "Body", with: "My first post body!"
click_button "Publish"
end
test 'can comment on post' do
visit post_path(Post.first) # should go to post created in last test
click_button "Add comment"
...
end
end
I have heard that this may be possible in Cucumber, but chose not to use Cucumber for other reasons, so want it to work with Minitest and Capybara.
Capybara::Rails::TestCase inherits from ActiveSupport::TestCase. One of ActiveSupport::TestCase's main features is that it runs each test in a database transaction. There are ways to work around this, but I would not recommend them.
Instead, I suggest you work with the behavior of the rails test classes. In this case, you want to share actions between tests. I recommend you extract those actions into methods and call those methods in your tests. Here is how I would implement this with your test code:
class BlogTest< Capybara::Rails::TestCase
def user
#user ||= User.create!(name: "user",
email: "user#example.com",
password: user_password,
password_confirmation: user_password)
end
def user_password
"passw0rd!"
end
def sign_in(email, password)
visit new_user_session_path
fill_in('Login', :with => email)
fill_in('Password', :with => password)
check('Remember me')
click_button('Sign in')
end
def create_post(title = "My first post title!",
body = "My first post body!")
visit new_post_path # how can I have user logged in?
fill_in "Title", with: title
fill_in "Body", with: body
click_button "Publish"
end
def comment_on_post(post, comment)
visit post_path(post)
click_button "Add comment"
# ...
end
test "can sign in" do
sign_in(user.email, user_password)
# add assertions here that you are signed in correctly
end
test "can't sign in with a bad password" do
sign_in(user.email, "Not the real password")
# add assertions here that you are not signed in
end
test "can create post when signed in" do
sign_in(user.email, user_password)
create_post
# add assertions here that post was created correctly
end
test "can't create post when not signed in" do
create_post
# add assertions here that post was not created
end
test "can comment on post when signed in" do
sign_in(user.email, user_password)
create_post
post = user.posts.order(:created_at).last
comment_on_post(post, "I can comment because I'm signed in!")
# add assertions here that comment was created correctly
end
test "can't comment on post when not signed in" do
post = Post.first
comment_on_post(post, "I can't comment because I'm not signed in!")
# add assertions here that comment was not created
end
end
Each action has a good name, and you can reuse those actions for different types of tests. Each test is executed within a database transaction, so each time the each test method is run the database looks the same.

Factory girl not initiating object

I guess the problem is that I do not know how to use factory girl with Rspec correctly. Or testing in rails correctly for that matter. Still think it is a bit weird though..
I have a class, User, with the following factory:
FactoryGirl.define do
factory :user do
name "admin"
email "admin#admin.com"
adminstatus "1"
password "foobar"
password_confirmation "foobar"
end
factory :user_no_admin, class: User do
name "user"
email "user#user.com"
adminstatus "2"
password "foobar"
password_confirmation "foobar"
end
...
My test looks like this:
...
describe "signin as admin user" do
before { visit login_path }
describe "with valid information" do
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
before do
fill_in "User", with: user.name
fill_in "Password", with: user.password
click_button "Login"
end
it "should list users if user is admin" do
response.should have_selector('th', content: 'Name')
response.should have_selector('td', content: user_no_admin.name)
response.should have_selector('td', content: user.name)
end
end
end#signin as admin user
...
Basically I am trying to test that if you log in as an admin, you should see a list of all the users. I have a test for logging on as a non-admin later on in the file. I have a couple of users in the db already.
In the list of users 'admin' that logged in is displayed along with the users already in the db. 'user' is however not displayed unless I do something like this before:
fill_in "User", with: user_no_admin.name
fill_in "Password", with: user_no_admin.password
It is as if it won't exist unless I use it. However, if I use a puts it does print the information I am putting, even if I do not do the 'fill_in' above.
I have a similar example where a puts helps me.
describe "should have company name" do
let(:company) { FactoryGirl.create(:company) }
let(:category) { FactoryGirl.create(:category) }
let(:company_category) { FactoryGirl.create(:company_category, company_id: company.id, category_id: category.id) }
it "should contain companies name" do
puts company_category.category_id
get 'categories/' + company.categories[0].id.to_s
response.should have_selector('h4', :content => company.name)
end
end
Without the puts above I get a
Called id for nil
Do I have to initiate(?) an object created by Factory girl before I can use it in some way?
Any other code needed?
let(:whatever)
Is not creating the objects until the first time you call them. If you want it to be available before first use, use
let!(:whatever)
instead.
Or use a before block:
before(:each) do
#company = FactoryGirl.create(:company)
....
end
Which will create the objects before you need to use them.
Instead of:
factory :user do
name "admin"
email "admin#admin.com"
...
I will do:
factory :user do |f|
f.name "admin"
f.email "admin#admin.com"
...
Instead of:
let(:user_no_admin) { FactoryGirl.create(:user_no_admin) }
let(:user) { FactoryGirl.create(:user) }
I will do:
#user_no_admin = Factory(:user_no_admin)
#user = Factory(:user)
I had a similar issue with an existing test I broke, with a slightly different cause that was interesting.
In this case, the controller under test was originally calling save, but I changed it to call save!, and updated the test accordingly.
The revised test was:
Declaring the instance a let statement
Setting an expectation on the save! method (e.g. expect_any_instance_of(MyObject).to receive(:save!) )
Using the instance for the first time after the expectation.
Internally, it would appear that FactoryGirl was calling the save! method, and after changing the expectation from save to save!, no work was actually done (and the code under test couldn't find the instance from the DB)
that I needed to update and had a hard time getting to actually pass without a hack)
Try to use trait in the factory girl,there is an example as mentioned in the this link

Rails 3.1, RSpec: testing model validations

I have started my journey with TDD in Rails and have run into a small issue regarding tests for model validations that I can't seem to find a solution to. Let's say I have a User model,
class User < ActiveRecord::Base
validates :username, :presence => true
end
and a simple test
it "should require a username" do
User.new(:username => "").should_not be_valid
end
This correctly tests the presence validation, but what if I want to be more specific? For example, testing full_messages on the errors object..
it "should require a username" do
user = User.create(:username => "")
user.errors[:username].should ~= /can't be blank/
end
My concern about the initial attempt (using should_not be_valid) is that RSpec won't produce a descriptive error message. It simply says "expected valid? to return false, got true." However, the second test example has a minor drawback: it uses the create method instead of the new method in order to get at the errors object.
I would like my tests to be more specific about what they're testing, but at the same time not have to touch a database.
Anyone have any input?
CONGRATULATIONS on you endeavor into TDD with ROR I promise once you get going you will not look back.
The simplest quick and dirty solution will be to generate a new valid model before each of your tests like this:
before(:each) do
#user = User.new
#user.username = "a valid username"
end
BUT what I suggest is you set up factories for all your models that will generate a valid model for you automatically and then you can muddle with individual attributes and see if your validation. I like to use FactoryGirl for this:
Basically once you get set up your test would look something like this:
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Here is a good railscast that explains it all better than me:
UPDATE: As of version 3.0 the syntax for factory girl has changed. I have amended my sample code to reflect this.
An easier way to test model validations (and a lot more of active-record) is to use a gem like shoulda or remarkable.
They will allow to the test as follows:
describe User
it { should validate_presence_of :name }
end
Try this:
it "should require a username" do
user = User.create(:username => "")
user.valid?
user.errors.should have_key(:username)
end
in new version rspec, you should use expect instead should, otherwise you'll get warning:
it "should have valid factory" do
expect(FactoryGirl.build(:user)).to be_valid
end
it "should require a username" do
expect(FactoryGirl.build(:user, :username => "")).not_to be_valid
end
I have traditionally handled error content specs in feature or request specs. So, for instance, I have a similar spec which I'll condense below:
Feature Spec Example
before(:each) { visit_order_path }
scenario 'with invalid (empty) description' , :js => :true do
add_empty_task #this line is defined in my spec_helper
expect(page).to have_content("can't be blank")
So then, I have my model spec testing whether something is valid, but then my feature spec which tests the exact output of the error message. FYI, these feature specs require Capybara which can be found here.
Like #nathanvda said, I would take advantage of Thoughtbot's Shoulda Matchers gem. With that rocking, you can write your test in the following manner as to test for presence, as well as any custom error message.
RSpec.describe User do
describe 'User validations' do
let(:message) { "I pitty da foo who dont enter a name" }
it 'validates presence and message' do
is_expected.to validate_presence_of(:name).
with_message message
end
# shorthand syntax:
it { is_expected.to validate_presence_of(:name).with_message message }
end
end
A little late to the party here, but if you don't want to add shoulda matchers, this should work with rspec-rails and factorybot:
# ./spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:username) { |n| "user_#{n}" }
end
end
# ./spec/models/user_spec.rb
describe User, type: :model do
context 'without a username' do
let(:user) { create :user, username: nil }
it "should NOT be valid with a username error" do
expect(user).not_to be_valid
expect(user.errors).to have_key(:username)
end
end
end

Resources