RSpec Testovi za Rails Kontroler

Rails je okvir za veb razvoj, gde su model , prikaz i kontroler važni aspekti vaše aplikacije. Kontrolere, baš kao i modele i prikaze, treba testirati pomoću omiljenog alata Rubi zajednice, RSpec .

Kontroleri u Rails-u prihvataju HTTP zahteve kao svoj ulaz i isporučuju natrag i HTTP odgovor kao izlaz.

Organizovanje testova

Blokovi opisa i konteksta su presudni za održavanje testova organizovanih u čistu hijerarhiju, zasnovanu na akcijama kontrolera i kontekstu koji testiramo. Betterspecs.org pruža osnove pisanja testova i pomoći će vam da testove učinite mnogo izražajnijim.

Svrha „opisivanja“ je umotavanje skupa testova protiv jedne funkcionalnosti, dok je „kontekst“ umotavanje skupa testova protiv jedne funkcionalnosti u isto stanje. Describe vs. Context in RSpec od Ming Liu

Želite da kreirate kontekst za svaki značajan ulaz i da ga umotate u blok za opis.

Mi ćemo izraziti svaki HTTP sesije u različitim opisuju blokova za: stories_controller_spec.rb.

describe "Stories" do
  describe "GET stories#index" do
    context "when the user is an admin" do
      it "should list titles of all stories"
    end
    
    context "when the user is not an admin" do
      it "should list titles of users own stories" do
    end

Kada želite da kontrolišete pristup autorizaciju, možete stvoriti novi kontekst za svaku korisničku ulogu. Na isti način možete da upravljate pristupom za potvrdu identiteta tako što ćete stvoriti novi kontekst za prijavljene i odjavljene korisnike.

context "when the user is logged in" do
  it "should render stories#index"
end

context "when the user is logged out" do
  it "should redirect to the login page"
end
end

Podrazumevano, konfiguracija RSpec-Rails onemogućava prikazivanje šablona za specifikacije kontrolera. Možete ga omogućiti dodavanjem render_views:

1.Globalno, dodavanjem za RSpec.configure blok u rails_helper.rb datoteci
2.Po pojedinačnoj grupi

  describe "GET stories#show" do
    it "should render stories#show template" do
    end
  end

  describe "GET stories#new" do
    it "should render stories#new template" do
    end
  end

Veoma je često proveriti da li koristite važeće ili nevaljane atribute pre nego što ih sačuvate u bazi podataka.

  describe "POST stories#create" do
    context "with valid attributes" do
      it "should save the new story in the database"
      it "should redirect to the stories#index page"
    end
    
    context "with invalid attributes" do
      it "should not save the new story in the database"
      it "should render stories#new template"
    end
  end
end

Kako pripremiti podatke?

Koristimo fabrike da bismo pripremili podatke za specifikacije naših kontrolera. Način rada fabrika može se poboljšati pomoću FactoryBot gema.

Sa sledećom fabrikom generisaćemo više priča koristeći sequencerazličite naslove i sadržaje:

FactoryBot.define do
  factory :story do
    user
    sequence(:title) { |n| "Title#{n}" }
    sequence(:content) { |n| "Content#{n}" }
  end
end

Da testiramo ovo!

Došlo je vreme da kreiramo sopstvene testove kontrolera. Testovi su napisani korišćenjem RSPEC i Capybara . Pokrićemo stories_controller.rb testove za svaku od ovih metoda:

#index

Prvo želimo da pogledamo naš kontroler stories_controller.rb. Akcija indeksa ovlašćuje pristup pričama u zavisnosti od toga da li je trenutni korisnik administrator:

def index
  @stories = Story.view_premissions(current_user).
end

I u modelu story.rb smo proverili da li je trenutni korisnik administrator:

def self.view_premissions(current_user)
  current_user.role.admin? ? Story.all : current_user.stories
end

Pomoću podataka koje smo upravo prikupili možemo stvoriti sledeći test GET stories#index:

describe "GET stories#index" do
  context "when the user is an admin" do
    it "should list titles of all stories" do
      admin = create(:admin)
      stories = create_list(:story, 10, user: admin)
      login_as(admin, scope: :user)
      visit stories_path

      stories.each do |story|
        page.should have_content(story.title)
      end
    end
  end

  context "when the user is not an admin" do
    it "should list titles of users own stories" do
      user = create(:user)
      stories = create_list(:story, 10, user: user)
      login_as(user, scope: :user)
      visit stories_path

      stories.each do |story|
        page.should have_content(story.title)
      end
    end
  end
end

Kao što vidite, stvorili smo dva različita konteksta za svaku korisničku ulogu (admin, i ne admin). Administratorski korisnik će moći da vidi sve naslove priča, s druge strane, standardni korisnici mogu da vide samo svoje.

Upotreba opcija create(:user) i create_list(:story, 10, user: user)možete kreirati korisnika i deset različitih priča za tog korisnika. Novostvoreni korisnik će se prijaviti login_as(user, scope: :user) i posetiti stories_path stranicu, gde može videti sve naslove priča, u zavisnosti od njegove trenutne uloge page.should have_content(story.title).

Još jedan odličan način za stvaranje novih korisnika je korišćenje blokova let ili before , to su dva različita načina za pisanje DRY testova.

#show

Na sličan način možete napisati testove metode #show. Jedina razlika je u tome što želite da pristupite stranici koja prikazuje priču koju želite da pročitate.

describe "GET stories#show" do
  it "should render stories#show template" do
    user = create(:user)
    story = create(:story, user: user)

    login_as(user, scope: :user)
    visit story_path(story.id)

    page.should have_content(story.title)
    page.should have_content(story.content)
  end
end

Još jednom želimo da stvorimo korisnika create(:user) i priču create(:story, user: user). Stvoreni korisnik će se prijaviti i posetiti stranicu koja sadrži priču na osnovu stori.id visit story_path(story.id).

#new i #create

Za razliku od ostalih, ovaj metod stvara novu priču. Proverimo sledeću akciju ustories_controller.rb

# GET stories#new
def new
  @story = Story.new
end
# POST stories#create
def create
  @story = Story.new(story_params)
  if @story.save
    redirect_to story_path(@story), success: "Story is successfully created."
  else
    render action: :new, error: "Error while creating new story"
  end
end

private

def story_params
  params.require(:story).permit(:title, :content)
end

new Akcija renderuje stories#new šablon, to je forma da popunite pre nego što se kreira nova priča pomoću akcije create. Nakon uspešnog kreiranja, priča će biti sačuvana u bazi podataka.

describe "POST stories#create" do
  it "should create a new story" do
    user = create(:user)
    login_as(user, scope: :user)
    visit new_stories_path

    fill_in "story_title", with: "Ruby on Rails"
    fill_in "story_content", with: "Text about Ruby on Rails"

    expect { click_button "Save" }.to change(Story, :count).by(1)
  end
end

Ovog puta kreirani i prijavljeni korisnik će posetiti stranicu na kojoj može stvoriti novu priču visit new_stories_path. Sledeći korak je popunjavanje obrasca naslovom i sadržajem fill_in "...", with: "...". Jednom kada kliknemo na dugme Spremi click_button "Save", broj ukupnih priča će se povećati za jednu change(Story, :count).by(1), što znači da je priča uspešno kreirana.

#update

Svi žele da mogu da ažuriraju svoje priče. To se lako može učiniti na sledeći način:

def update
  if @story.update(story_params)
    flash[:success] = "Story #{@story.title} is successfully updated."
    redirect_to story_path(@story)
  else
    flash[:error] = "Error while updating story"
    redirect_to story_path(@story)
  end
end

private

def story_params
  params.require(:story).permit(:title, :content)
end

Kada se stvori nova priča, moći ćemo je ažurirati posetom stranici za uređivanje priča.

describe "PUT stories#update" do
  it "should update an existing story" do
    user = create(:user)
    login_as(user, scope: :user)
    story = create(:story)
    visit edit_story_path(story)
    
    fill_in "story_title", with: "React"
    fill_in "story_content", with: "Text about React"
    
    click_button "Save"
    expect(story.reload.title).to eq "React"
    expect(story.content).to eq "Text about React"
  end
end

Baš kao i u prethodnim metodama, novostvoreni prijavljeni korisnik će stvoriti priču i posetiti stranicu za uređivanje priče edit_story_path(story). Kada ažuriramo naslov i sadržaj priče, očekuje se da će se promeniti kako smo tražili expect(story.reload.title).to eq "React".

#delete

Napokon želimo da možemo da izbrišemo priče koje nam se nisu svidele.

def destroy
  authorize @story
  if @story.destroy
    flash[:success] = "Story #{@story.title} removed successfully"
    redirect_to stories_path
  else
    flash[:error] = "Error while removing story!"
    redirect_to story_path(@story)
  end 
end

Želite da budete sigurni da samo administrator i vlasnik priče mogu da je izbrišu instaliranjem gema 'pundit'.

class StoryPolicy < ApplicationPolicy
  def destroy?
    @user.role.admin?
  end
end

Isprobajmo i ovo.

describe "DELETE stories#destroy" do
  it "should delete a story" do
    user = create(:admin)
    story = create(:story, user: user)
    login_as(user, scope: :user)
    visit story_path(story.id)
    page.should have_link("Delete")
    expect { click_link "Delete" }.to change(Story, :count).by(-1)
  end
end

Test je napisan na sličan način kao za stories#create, sa velikom razlikom. Umesto da kreiramo priču, mi je brišemo i tako smanjujemo ukupan broj za jedan change(Story, :count).by(-1).

Još jednom smo stigli do kraja! Ali ima za vas još mnogo članaka, pretplatite se odmah!