Rails is a web development framework, where , and are important aspects of your application. Controllers, just like models and viewers, need to be tested with Ruby communities favorite tool, . model view controller RSpec in Rails accept as their input and deliver back and as an output. Controllers HTTP requests HTTP response Organizing tests and are crucial for keeping tests organized into a clean hierarchy, based on a controller’s actions and the context we’re testing. provides the basics about writing your tests, it will help you make your tests much more expressive. Describe context blocks Betterspecs.org The purpose of ‘describe’ is to wrap a set of tests against one functionality while ‘context’ is to wrap a set of tests against one functionality under the same state. by Ming Liu. Describe vs. Context in RSpec You want to create a context for each meaningful input and wrap it into a describe block. We will express each HTTP session in different describe blocks for: . 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 When you want to control the you can create a new context for each user role. In the same way, you can manage the , by creating a new context for logged in and logged out users. authorization access authentication access 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 By default, disables rendering of templates for controller specs. You can enable it by adding : RSpec-Rails configuration render_views Globally, by adding it to block in file RSpec.configure rails_helper.rb Per individual group 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 It is very common to check if you are using valid or invalid attributes before saving them to the database. 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 How to get your data ready? We use to get the data ready for our controller specs. The way factories work can be improved with a gem. factories FactoryBot With the following factory we will generate multiple stories by using a of different titles and contents: sequence FactoryBot.define do factory :story do user sequence(:title) { |n| "Title#{n}" } sequence(:content) { |n| "Content#{n}" } endend Let’s test this out! The time has come to create our own controller tests. The tests are written using and . We will cover with tests for each of these methods: RSpec Capybara stories_controller.rb #index First, we want to take a look at our controller . The index action to stories depending if the current user is an admin: stories_controller.rb authorizes access def index @stories = Story.view_premissions(current_user).end And in model we if the current user is an admin: story.rb check def self.view_premissions(current_user) current_user.role.admin? ? Story.all : current_user.storiesend With the info we just gathered, we can create the following 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 endend 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 endend As you can see, we created two different contexts for each user role (admin and not admin). The admin user will be able to see all the story titles, on the other hand, standard users can only see their own. Using options and you can create users and ten different stories for that user. The newly created user will login and visit the page, where he can see all the story titles depending on his current role . create(:user) create_list(:story, 10, user: user) login_as(user, scope: :user) stories_path page.should have_content(story.title) Another great way to create new users is using , those are two different ways to write DRY tests. let or before blocks #show You can write the #show method tests in a similar way. The only difference is that you want to access the page that shows the story you want to read. 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) endend Once again we want to create the user and a story . The created user will log in and visit the page that contains the story based on the story.id . create(:user) create(:story, user: user) visit story_path(story.id) #new and #create Unlike the others, this method creates a new story. Let’s check out the following action in stories_controller.rb # GET stories#newdef new @story = Story.newend # POST stories#createdef 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" endend private def story_params params.require(:story).permit(:title, :content)end The action renders a stories#new template, it is a form that you fill out before creating a new story using the action. On successful creation, the story will be saved in the database. new create 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) endend This time a created and logged in user will visit the page where it can create a new story . The next step is to fill up the form with title and content . Once we click on the save button , the number of total stories will increase by one , meaning that the story was successfully created. visit new_stories_path fill_in "...", with: "..." click_button "Save" change(Story, :count).by(1) Everyone wants to be able to update their stories. This can be easily done in the following way: 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) endend private def story_params params.require(:story).permit(:title, :content)end When a new story is created we will be able to update it, by visiting the stories edit page. 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" endend Just like in the previous methods, a newly created logged in user will create a story and visit the edit story page . Once we update the title and content of the story it is expected to change as we asked . edit_story_path(story) expect(story.reload.title).to eq "React" #delete At last, we want to be able to delete the stories we disliked. 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) endend You want to make it sure that only the admin and owner of the story can delete it, by . installing [gem 'pundit'](https://kolosek.com/rails-bundle-install-and-gemfile/) class StoryPolicy < ApplicationPolicy def destroy? @user.role.admin? endend Let’s test this out as well. 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) endend The test is written in a similar way to stories#create, with a major difference. Instead of creating the story, we delete it and such reduce the overall count by one . change(Story, :count).by(-1) Once again we reached the end! But there are many more articles waiting for you, subscribe now! Originally published at kolosek.com on February 22, 2018.