I have problem in my previous question, me helped, but and now I've took new.
I'm make integration tests with rspec and capybara.
this my profiles_controllers.rb :
before_filter :authenticate_user!
def update
#profile = current_user.profile
if #profile.update_attributes(params[:profile])
flash[:success] = "Профиль обновлен!"
redirect_to user_path(current_user)
else
render 'edit'
end
end
it's my test file:
describe "ProfilePages" do
subject { page }
describe "edit" do
let(:user) { FactoryGirl.create(:user) }
let(:profile) { FactoryGirl.create(:profile, user: user) }
before do
login user
visit edit_profile_path(profile)
end
it { should have_selector('h2', text: 'Заполните информацию о себе') }
describe "change information" do
let(:new_city) { "Ulan-Bator" }
let(:new_phone) { 1232442 }
let(:new_gamelevel) { "M2" }
let(:new_aboutme) { "nfsfsdfds" }
let(:submit) { "Сохранить" }
before do
fill_in "Город", with: new_city
fill_in "Телефон", with: new_phone
select new_gamelevel, from: "Уровень игры"
fill_in "О себе", with: new_aboutme
click_button submit
end
specify { profile.reload.city.should == new_city }
specify { profile.reload.phone.should == new_phone }
specify { profile.reload.gamelevel.should == new_gamelevel }
specify { profile.reload.aboutme.should == new_aboutme }
end
describe "submitting to the update action" do
before { put profile_path(profile) }
specify { response.should redirect_to(user_path(user)) }
end
end
end
And I have error:
Failure/Error: specify { response.should redirect_to(user_path(user)) }
Expected response to be a redirect to http://www.example.com/users/1 but was a redirect to http://www.example.com/users/sign_in
I use Devise and have login helper in spec/support:
def login(user)
page.driver.post user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
And config.include Devise::TestHelpers, :type => :controller in spec_helper.rb
I tried use warden helper login_as , but have same error. How I understand it's don't start session, I'am right?
This is nothing to do with your app code, but the test code.
response object is for controller integration tests, and there is no such object in Capybara.
Normally you can use page object to check response information. And for path checking, a better approach is current_path or current_url.
So your code will work by:
current_path.should be(user_path(user))
Related
I'm attempting to test a line of code in a proc see (proc { |message| flash[:notice] << message } in the code snippet below) using Rspec 3.9 but I can't seem to stub out the instance to do what I want.
Given the following controller and test, how can I stub out CreateAccount and run the on_success attribute given in the controller?
Here is the controller file
class AccountsController < ApplicationController
def create
CreateAccount.new(
on_success: proc { |message| flash[:notice] << message }
).process
redirect_to action: :index
end
end
Here is the Rspec test file
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
let(:success_message) { 'Success!' }
context 'when created account successfully' do
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include success_message
end
end
end
end
The reason I want to do this is to separate concerns from the controller to the CreateAccount object. It shouldn't matter, but here is the CreateAccount object so far.
class CreateAccount
def initialize on_success: proc { |_| }
#on_success = on_success
end
def call
# Do some meaningful work
success_message = 'Meaningful message'
#on_success.call(success_message)
end
end
I've managed to find 1 solution that works but I would like to know if there's a cleaner way
Note: The controller and CreateAction classes remain the same.
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
context 'when created account successfully' do
let(:dummy_class) do
Class.new(CreateAction) do
def call
#on_success.call(self.class.message)
end
def self.message
'Success!'
end
end
end
before { stub_constant 'CreateAction', dummy_class }
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include dummy_class.message
end
end
end
end
I have a two-factor verification page, a secret key(Ciphertext) is displayed on it and I already have clipboard.js installed in my application.
I wonder how it is possible to create a button to copy that secret key?
= simple_form_for #google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
h4 = t('.step-1')
p
span = t('.download-app')
span == t('.guide-link')
h4 = t('.step-2')
p: span = t('.scan-qr-code')
= f.input :uri do
= qr_tag(#google_auth.uri)
= f.input :otp_secret do
.input-group
= f.input_field :otp_secret, class: 'upcase', readonly: true
span.input-group-btn
a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
i.fa.fa-refresh
h4 = t('.step-3')
p: span = t('.enter-passcode')
= f.input :otp
hr.split
= f.button :wrapped, t('.submit'), cancel: settings_path
= content_for :guide do
ul.list-unstyled
li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
i.fa.fa-apple
span = t('.ios')
li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
i.fa.fa-android
span = t('.android')
I tried to do like this, but it didn't work:
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target=':otp_secret'
i.fa.fa-clipboard
In the example above it is copying only the pure otp_secret text.
spec\models\two_factor\app_spec.rb:
require 'spec_helper'
describe TwoFactor::App do
let(:member) { create :member }
let(:app) { member.app_two_factor }
describe "generate code" do
subject { app }
its(:otp_secret) { should_not be_blank }
end
describe '#refresh' do
context 'inactivated' do
it {
orig_otp_secret = app.otp_secret.dup
app.refresh!
expect(app.otp_secret).not_to eq(orig_otp_secret)
}
end
context 'activated' do
subject { create :two_factor_app, activated: true }
it {
orig_otp_secret = subject.otp_secret.dup
subject.refresh!
expect(subject.otp_secret).to eq(orig_otp_secret)
}
end
end
describe 'uniq validate' do
let(:member) { create :member }
it "reject duplicate creation" do
duplicate = TwoFactor.new app.attributes
expect(duplicate).not_to be_valid
end
end
describe 'self.fetch_by_type' do
it "return nil for wrong type" do
expect(TwoFactor.by_type(:foobar)).to be_nil
end
it "create new one by type" do
expect {
expect(app).not_to be_nil
}.to change(TwoFactor::App, :count).by(1)
end
it "retrieve exist one instead of creating" do
two_factor = member.app_two_factor
expect(member.app_two_factor).to eq(two_factor)
end
end
describe '#active!' do
subject { member.app_two_factor }
before { subject.active! }
its(:activated?) { should be_true }
end
describe '#deactive!' do
subject { create :two_factor_app, activated: true }
before { subject.deactive! }
its(:activated?) { should_not be_true }
end
describe '.activated' do
before { create :member, :app_two_factor_activated }
it "should has activated" do
expect(TwoFactor.activated?).to be_true
end
end
describe 'send_notification_mail' do
let(:mail) { ActionMailer::Base.deliveries.last }
describe "activated" do
before { app.active! }
it { expect(mail.subject).to match('Google authenticator activated') }
end
describe "deactived" do
let(:member) { create :member, :app_two_factor_activated }
before { app.deactive! }
it { expect(mail.subject).to match('Google authenticator deactivated') }
end
end
end
app.rb:
class TwoFactor::App < ::TwoFactor
def verify?
return false if otp_secret.blank?
rotp = ROTP::TOTP.new(otp_secret)
if rotp.verify(otp)
touch(:last_verify_at)
true
else
errors.add :otp, :invalid
false
end
end
def uri
totp = ROTP::TOTP.new(otp_secret)
totp.provisioning_uri(member.email) + "&issuer=#{ENV['URL_HOST']}"
end
def now
ROTP::TOTP.new(otp_secret).now
end
def refresh!
return if activated?
super
end
private
def gen_code
self.otp_secret = ROTP::Base32.random_base32
self.refreshed_at = Time.new
end
def send_notification
return if not self.activated_changed?
if self.activated
MemberMailer.google_auth_activated(member.id).deliver
else
MemberMailer.google_auth_deactivated(member.id).deliver
end
end
end
EDIT:
app\models\two_factor.rb:
class TwoFactor < ActiveRecord::Base
belongs_to :member
before_validation :gen_code, on: :create
after_update :send_notification
validates_presence_of :member, :otp_secret, :refreshed_at
attr_accessor :otp
SUBCLASS = ['app', 'sms', 'email', 'wechat']
validates_uniqueness_of :type, scope: :member_id
scope :activated, -> { where(activated: true) }
scope :require_signin, -> { where(require_signin: 1) }
class << self
def by_type(type)
return if not SUBCLASS.include?(type.to_s)
klass = "two_factor/#{type}".camelize.constantize
klass.find_or_create_by(type: klass.name)
end
def activated?
activated.any?
end
def require_signin?
require_signin.any?
end
end
def verify?
msg = "#{self.class.name}#verify? is not implemented."
raise NotImplementedError.new(msg)
end
def expired?
Time.now >= 30.minutes.since(refreshed_at)
end
def refresh!
gen_code
save
end
def active!
update activated: true, last_verify_at: Time.now
end
def set_require_signin
update require_signin: 1
end
def reset_require_signin
update require_signin: nil
end
def deactive!
update activated: false, require_signin: nil
end
private
def gen_code
msg = "#{self.class.name}#gen_code is not implemented."
raise NotImplementedError.new(msg)
end
def send_notification
msg = "#{self.class.name}#send_notification is not implemented."
raise NotImplementedError.new(msg)
end
end
What it seems you're trying to do is just to copy the value of an input field(which has been populated by other code you have) to the system clipboard. You need to use javascript to do this, if you have jquery this should work.
For your slim you need an id to target it
a.btn.btn-default id= "copy"
i.fa.fa-clipboard
Try to add an id to the input element you want to copy from
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
Now try to change this and see if works.
a.btn.btn-default data-clipboard-action='copy' data-clipboard-target='secret'
i.fa.fa-clipboard
Also somewhere in your javascript you'll need to target the clip event with something like this:
new ClipboardJS('#secret');
See example here https://jsfiddle.net/ec3ywrzd/
Then you'll need this javascript to load in your html. But you'll need to be able to target the cipher field, in this example I'm using id="secret". I'm not sure if the OTP code you have generates it's own ID or now, so you may need to inspect your dom to figure out how to target it to add an ID. You may try adding an ID here:
= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true
Otherwise you'll have to use other query selectors to target it.
But you may not need clipboardjs at all.
Here's a basic example on jsfiddle to test it you can just add any string to the input field. You'll need to add this to a JS file which will be loaded by your view layout, i.e. application.js
$(document).ready(function() {
$('#copy').click(function(){
$('#secret').select();
document.execCommand('copy');
alert("copied!");
})
})
You may also see answers to this question
I managed to solve based on suggestions from our friend #lacostenycoder.
There was only a need to change even in the show.html.slim file, looking like this:
= simple_form_for #google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
h4 = t('.step-1')
p
span = t('.download-app')
span == t('.guide-link')
h4 = t('.step-2')
p: span = t('.scan-qr-code')
= f.input :uri do
= qr_tag(#google_auth.uri)
= f.input :otp_secret do
.input-group
.form-control.form-control-static = #google_auth.otp_secret
.input-group
a.btn.btn-default href="javascript:void(0)" data-clipboard-text = #google_auth.otp_secret
i.fa.fa-clipboard
a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
i.fa.fa-refresh
h4 = t('.step-3')
p: span = t('.enter-passcode')
= f.input :otp
hr.split
= f.button :wrapped, t('.submit'), cancel: settings_path
= content_for :guide do
ul.list-unstyled
li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
i.fa.fa-apple
span = t('.ios')
li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
i.fa.fa-android
span = t('.android')
i want to test an update action in my rails project. The function is running well, but when i try to unit-test it's always fail. Can you help this?
i'm new with ruby and unit-testing. i've tried with any documentation it still not solve this test.
this is my Content Controller function that i want to test, i use clearance for auth
before_action :require_login
def update
#content = Content.find(params[:id])
if #content.update(content_params)
flash[:notice] = 'success'
redirect_back fallback_location: root_path
else
flash[:notice] = 'fail'
redirect_back fallback_location: root_path
end
end
this is my Content Fixture
one:
id: '1'
about: 'about content'
privacy: 'privacy content'
terms: 'terms content'
this is my test_helper
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
require 'clearance/test_unit'
class ActiveSupport::TestCase
fixtures :all
end
class ActionDispatch::IntegrationTest
def sign_in_as_user
user = User.create!(email: "example#example.com", password: "letmein")
post session_url, params: {
session: {
email: user.email,
password: user.password
}
}
end
end
And this is my test code
test "should update content" do
sign_in_as_user
content = contents(:one)
put content_url(content), params: { about: { syarat: "updated about", privacy: "updated privacy", terms: "updated terms" } }
content.reload
assert_equal "updated about", content.about
assert_equal "updated privacy", content.privacy
assert_equal "updated terms", content.terms
end
Then i run the test rake test, this is the result
Failure:
ContentControllerTest#test_should_update_content [/Users/abc/Documents/PROJECT/Apps/test-app/test/controllers/content_controller_test.rb:20]:
--- expected
+++ actual
## -1 +1 ##
-"updated about"
+"about content"
I want to implement rspec with expect. I tried this:
RSpec:
describe WechatRequestBuilder do
let(:request_builder) { described_class.new(env: 'test_env') }
let(:trx_types) { ['wechat'] }
let(:trx_type) { 'wechat' }
let(:gateway) { 'wechat' }
let(:currency) { 'CNY' }
let(:base_params) { request_builder.send(:base_params) }
it_behaves_like 'request builder', true
context '#submit!' do
it "sends test transactions" do
allow(request_builder).to receive(:process_trx).with(trx_types, gateway)
binding.pry
request_builder.submit!
expect(request_builder.submit!).to receive(:process_trx).with(trx_types, gateway)
end
end
end
Request modifier:
class RequestModifier
def get_trx_type(request_body)
doc = Nokogiri::XML(request_body)
doc.search("transaction_type").first.text
end
end
I tried to find some object with binding.pry but without a luck:
[1] pry(#<RSpec::ExampleGroups::WechatRequestBuilder::Submit>)> request_builder
=> #<WechatRequestBuilder:0x007ffc1af4fd80 #env="test_env", #request_modifier=#<RequestModifier:0x007ffc1af4fd30>>
Can you give e some example based on the above code what should I configure as 'expect'? Currently I get:
(nil).process_trx(["wechat"], "wechat")
expected: 1 time with arguments: (["wechat"], "wechat")
received: 0 times
So I am working through the Ruby on Rails 3 Tutorial. I am currently on section 7.1.3 Testing the User show page using factories.
The code is working and pulls the proper gravatar image however I keep getting an error when running my tests.
Here is the error:
Failure/Error: before { visit user_path(user) }
ActionView::Template::Error:
undefined method `downcase' for nil:NilClass
Here is the code from the show.html.erb file:
<% provide(:title, #user.name) %>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
<%= #user.name %>, <%= #user.email %>
Here is the code from the users_helper.rb file:
module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
Here is the code from factories.rb file:
FactoryGirl.define do
factory :user do
name "Curtis Test"
email "test#gmail.com"
password "foobar"
password_confirmation "foobar"
end
end
Here is the code from the test file user_pages_spec.rb
require 'spec_helper'
describe "User Pages" do
subject { page }
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
end
describe "signup page" do
before { visit signup_path }
it { should have_selector('title', text: full_title('Sign Up')) }
end
end
I discovered my problem. It had nothing to do with FactoryGirl. The problem was in my user model (user.rb), the line that was causing the issue was
before_save { |user| user.email = user.email.downcase! }
The bang after the downcase was causing the email address to be saved as nil since the return of the downcase! is nil. Once I removed that and made the line look like the following it worked just fine.
before_save { |user| user.email = user.email.downcase }
The way I found it was to load the rails console in test environment and tried to create a new user. I noticed that everything was fine but the email was null.
In general, you can debug issues such as this one by referring to the Rails Tutorial sample app reference implementation.