2009-08-13 18 views
16

Sto lavorando su alcune storie di Cucumber per un'applicazione di registrazione che prevede una serie di passaggi.Variabili di sessione con Storie di cetriolo

Piuttosto che scrivere una storia Huuuuuuu per coprire tutti i passaggi contemporaneamente, che sarebbe difettoso, preferirei lavorare attraverso ogni azione nel controller come un utente normale. Il mio problema qui è che sto memorizzando l'ID account che viene creato nel primo passaggio come variabile di sessione, quindi quando vengono visitati il ​​passaggio 2, il passaggio 3 ecc. Vengono caricati i dati di registrazione esistenti.

Sono consapevole di poter accedere a controller.session[..] all'interno di specifiche RSpec, tuttavia quando provo a farlo nelle storie di Cucumber fallisce con il seguente errore (e, ho anche letto da qualche parte questo è un anti-pattern ecc. ..):

Utilizzando controller.session [: qualunque cosa] o una sessione [: qualunque cosa]

You have a nil object when you didn't expect it! 
The error occurred while evaluating nil.session (NoMethodError) 

Utilizzando sessione (: qualunque cosa)

wrong number of arguments (1 for 0) (ArgumentError) 

Quindi, sembra che l'accesso al negozio di sessioni non sia realmente possibile. Quello che mi chiedo è se potrebbe essere possibile (e credo che sarebbe meglio ..):

  1. Mock il negozio sessione ecc
  2. dispone di un metodo all'interno del controller e stub che fuori (ad esempio get_registration che assegna una variabile di istanza ...)

ho guardato attraverso il libro RSpec (beh, scremato) e aveva uno sguardo attraverso Webrat ecc, ma non ho davvero trovato una risposta al mio problema ...

Per chiarire un po 'di più, il processo di registrazione è più come una macchina a stati - ad es. l'utente avanza attraverso quattro passaggi prima che la registrazione sia completata - quindi 'logging' non è realmente un'opzione (rompe il modello di come funziona il sito) ...

Nella mia specifica per il controller sono riuscito per eliminare la chiamata al metodo che carica il modello in base alla variabile var, ma non sono sicuro che la riga "antipattern" si applichi anche agli stub e ai mock?

Grazie!

risposta

19

I mock sono cattivi in ​​scenari di cetrioli: sono quasi una specie di antipattern.

Il mio suggerimento è quello di scrivere un passo che registra in realtà un utente. Lo faccio in questo modo

Given I am logged in as "[email protected]" 

Given /^I am logged in as "(.*)"$/ do |email| 
    @user = Factory(:user, :email => email) 
    @user.activate! 
    visit("/session/new") 
    fill_in("email", :with => @user.email) 
    fill_in("password", :with => @user.password) 
    click_button("Sign In") 
end 

mi rendo conto che l'istanza variabile @user è una specie di cattiva forma, ma penso che nel caso di entrare/uscire, avere @user è decisamente utile.

A volte lo chiamo @current_user.

+1

Grazie per l'ingresso ... Ho aggiornato la mia domanda con qualche dettaglio in più, ma quello che hai suggerito non aiuta in quanto comporterebbe l'aggiunta di funzionalità specificamente per i test, che a me sembrano più di un 'anti-pattern' - ma, eliminando i controller, i metodi infrangono questa regola? –

+1

funzionalità aggiuntiva? Come fa un utente ad accedere al tuo sito? Si cita il processo di registrazione ma non si effettua il login. Perché non puoi creare una fabbrica per un utente già registrato e registrarti come ti ho mostrato sopra? Le storie di cetrioli devono esprimere esattamente come l'utente interagisce con il tuo sito. Un utente non può eliminare i metodi del controller. Hanno moduli da compilare e link per fare clic. Non è un test di accettazione se non imita il comportamento dell'utente. – danpickett

+0

Vedo entrambi i lati qui.Ho quasi esattamente il precedente per la funzionalità che verifica l'autenticazione funziona nel modo che voglio. Per altre caratteristiche, sembra un dolore dover ripetere quei passaggi di capibara per accedere ad ogni scenario. Cucumber può manipolare lo stato del database ai fini di un test, perché non la sessione? –

24

Ripeterò danpickett nel dire che i mock dovrebbero essere evitati quando possibile in Cetriolo. Tuttavia, se la tua app non ha una pagina di accesso, o forse la prestazione è un problema, allora potrebbe essere necessario simulare direttamente il login.

Questo è un brutto trucco, ma dovrebbe portare a termine il lavoro.

Given /^I am logged in as "(.*)"$/ do |email| 
    @current_user = Factory(:user, :email => email) 
    cookies[:stub_user_id] = @current_user.id 
end 

# in application controller 
class ApplicationController < ActionController::Base 
    if Rails.env.test? 
    prepend_before_filter :stub_current_user 
    def stub_current_user 
     session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] 
    end 
    end 
end 
+0

Utilizzato semplicemente per saltare l'accesso per un sito OAuth. Brillante. Grazie, Ryan. –

3

La mia comprensione è che si ottiene:

You have a nil object when you didn't expect it! 
The error occurred while evaluating nil.session (NoMethodError) 

quando la sessione [] si accede prima richiesta è stata creata un'istanza. Nel tuo caso, immagino che se metti il ​​numero visit some_existing_path di webrats prima di accedere alla sessione [] nella tua defenizione, l'errore andrà via.

Ora, purtroppo, la sessione non sembra persistere attraverso passi (almeno, io non riuscivo a trovare la via), quindi questo po 'di informazioni non aiuta a rispondere alla tua domanda :)

Così , Suppongo, il numero session[:user_id] = cookies[:stub_user_id]... di Ryan sia la strada da percorrere. Sebbene, imo, il codice relativo al test nell'applicazione stessa non suoni correttamente.

5

Non so quanto questo si riferisce alla domanda iniziale più, ma ho deciso di postare comunque nello spirito della discussione ...

Abbiamo una suite di test di cetriolo che prende> 10 minuti per correre quindi volevamo fare un po 'di ottimizzazione. Nella nostra app il processo di login attiva MOLTE funzionalità extra che sono irrilevanti per la maggior parte degli scenari, quindi abbiamo voluto saltarlo impostando direttamente l'id utente della sessione.

L'approccio di Ryanb in precedenza funzionava bene, tranne che non eravamo in grado di disconnettersi utilizzando tale approccio. Ciò ha reso le nostre storie multiutente fallite.

abbiamo finito per la creazione di un "accesso rapido" percorso che è abilitato solo in ambiente di test:

# in routes.rb 
map.connect '/quick_login/:login', :controller => 'logins', :action => 'quick_login' 

Ecco l'azione corrispondente che crea la variabile di sessione:

# in logins_controller.rb 
class LoginsController < ApplicationController 
    # This is a utility method for selenium/webrat tests to speed up & simplify the process of logging in. 
    # Please never make this method usable in production/staging environments. 
    def quick_login 
    raise "quick login only works in cucumber environment! it's meant for acceptance tests only" unless Rails.env.test? 
    u = User.find_by_login(params[:login]) 
    if u 
     session[:user_id] = u.id 
     render :text => "assumed identity of #{u.login}" 
    else 
     raise "failed to assume identity" 
    end 
    end 
end 

Per noi questo finito per essere più semplice di lavorare con l'array di cookie. Come bonus, questo approccio funziona anche con Selenium/Watir.

Il lato negativo è che stiamo includendo codice relativo ai test nella nostra applicazione. Personalmente non penso che aggiungere codice per rendere l'applicazione più testabile sia un peccato enorme, anche se aggiunge un po 'di confusione. Forse il problema più grande è che i futuri autori di test devono capire quale tipo di accesso dovrebbero usare. Con prestazioni hardware illimitate ovviamente non faremmo nulla di tutto ciò.

+0

Idea interessante, e potrebbe esserci un'alternativa al peppering del codice pronto per la produzione con il codice di test - forse all'avvio dei test è possibile iniettare la nuova rotta e class_eval il controller dell'applicazione per gestire il codice quicklogin ... Just un pensiero comunque;) Grazie per il tuo contributo! –

16

Re. La soluzione di Ryan - si può aprire ActionController in voi env.rb di file e metterlo lì per evitare di mettere nella vostra base di codice di produzione (grazie a laboratori di john @ cardine)

# in features/support/env.rb 
class ApplicationController < ActionController::Base 
    prepend_before_filter :stub_current_user 
    def stub_current_user 
    session[:user_id] = cookies[:stub_user_id] if cookies[:stub_user_id] 
    end 
end 
+0

Sono in Rails 4 e sto osservando che questo "impedisce" alle mie funzioni esistenti di "before_filter" di accadere, oltre a impedire che il mio "helper_method's esista. – Narfanator

2

io uso un test-only sign-in soluzione come Prikka's, ma faccio tutto in rack invece di creare un nuovo controller e percorsi.

# in config/environments/cucumber.rb: 

config.middleware.use (Class.new do 
    def initialize(app); @app = app; end 
    def call(env) 
    request = ::Rack::Request.new(env) 
    if request.params.has_key?('signed_in_user_id') 
     request.session[:current_user_id] = request.params['signed_in_user_id'] 
    end 
    @app.call env 
    end 
end) 

# in features/step_definitions/authentication_steps.rb: 
Given /^I am signed in as ([^\"]+)$/ do |name| 
    user = User.find_by_username(name) || Factory(:user, :username => name) 
    sign_in_as user 
end 

# in features/step_definitions/authentication_steps.rb: 
Given /^I am not signed in$/ do 
    sign_in_as nil 
end 

module AuthenticationHelpers 
    def sign_in_as(user) 
    return if @current_user == user 
    @current_user = user 
    get '/', { 'signed_in_user_id' => (user ? user.to_param : '') } 
    end 
end 

World(AuthenticationHelpers) 
4

Re: La soluzione di Ryan:

Non funziona con Capybara, a meno che piccolo adattamento fatto:

rack_test_driver = Capybara.current_session.driver 
cookie_jar = rack_test_driver.current_session.instance_variable_get(:@rack_mock_session).cookie_jar 
@current_user = Factory(:user) 
cookie_jar[:stub_user_id] = @current_user.id 

(trovato qui: https://gist.github.com/484787)

+0

Grazie per aver contribuito di nuovo alla tua soluzione, mi ha aiutato molto, funziona come un fascino. – Pedro

+1

Per me è solo l'attivazione di un errore: 'metodo non definito 'current_session' per # ' Potresti essere più specifico su dove nella tua applicazione devi inserire questo codice? – Ajedi32

0

Un'altra leggera variazione:

# In features/step_definitions/authentication_steps.rb: 

class SessionsController < ApplicationController 
    def create_with_security_bypass 
    if params.has_key? :user_id 
     session[:user_id] = params[:user_id] 
     redirect_to :root 
    else 
     create_without_security_bypass 
    end 
    end 

    alias_method_chain :create, :security_bypass 
end 

Given %r/^I am logged in as "([^"]*)"$/ do |username| 
    user = User.find_by_username(username) || Factory(:user, :username => username) 
    page.driver.post "/session?user_id=#{user.id}" 
end 
+1

Ho ricevuto questo errore! 'metodo non definito 'post' per # (NoMethodError)'. Sembra che non possiamo postare con il selenio, almeno. –

1

Perché non usi FactoryGirl o (Fixjour o Fabricator) con Devise (o Authlogic) e SentientUser? Quindi puoi semplicemente annusare quale utente ha già effettuato l'accesso!

@user = Factory(:user)  # FactoryGirl 
sign_in @user    # Devise 
User.current.should == @user # SentientUser 
2

@ Ajedi32 Ho incontrato lo stesso problema (metodo non definito 'current_session' per Capybara :: :: RackTest Driver) e mettere questo in mia definizione passo risolto il problema per me:

rack_test_browser = Capybara.current_session.driver.browser 

cookie_jar = rack_test_browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar 
cookie_jar[:stub_user_id] = @current_user.id 

Nell'azione con il mio controller, ho fatto riferimento ai cookie [: stub_user_id], invece di cookie_jar [: stub_user_id]

0

Dopo molte ricerche sull'anima e navigazione sul Web, ho finalmente optato per una soluzione molto semplice e ovvia.

L'utilizzo dei cookie aggiunge due problemi. Prima di tutto hai il codice nell'applicazione specifica per i test e in secondo luogo c'è il problema che la creazione di cookie in Cucumber è difficile quando si usa qualcosa di diverso dal test del rack. Ci sono varie soluzioni al problema dei cookie, ma tutte sono un po 'impegnative, altre introducono mock e tutte sono quelle che io chiamo "complicate". Una di queste soluzioni è here.

La mia soluzione è la seguente. Questo utilizza l'autenticazione di base HTTP ma potrebbe essere generalizzato per quasi tutto.

authenticate_or_request_with_http_basic "My Authentication" do |user_name, password| 
    if Rails.env.test? && user_name == 'testuser' 
     test_authenticate(user_name, password) 
    else 
     normal_authentication 
    end 
    end 

test_authenticate esegue sempre l'autenticazione normale eccetto che ignora le parti che consumano tempo. Nel mio caso, la vera autenticazione sta usando LDAP che volevo evitare.

Sì ... è un po 'volgare ma è chiaro, semplice e ovvio. E ... nessuna altra soluzione che ho visto è più pulita o più chiara.

Nota: una caratteristica è che se user_name non è 'testuser', allora viene preso il percorso normale in modo che possano essere testati.

Spero che questo aiuta gli altri ...

Problemi correlati