A Brief Introduction to Basics of RSpec in Ruby On Rails
A Brief Introduction to Basics of RSpec in Ruby On Rails
RSpec is a library that focuses on testing the behavior of your everyday projects for Ruby programming language. Developers are usually reluctant to use it because probably testing practice is the most difficult mindset a developer has to learn.
But once Rspec testing habit is developed and Test Driven Development is in , it becomes really useful in diagnosing problems in large scale applications once they are modified for some changes in future.
In the long term, a developer will not have to check each web page every time a new feature is added, but he will only check if the specific test passed and so handling of large applications becomes a convenient task.
Born under the banner of Behavior-Driven Development, Rspec is a productive and enjoyable automated testing experience with features like:
-a rich command line program (the rspec command)
-textual descriptions of examples and groups (rspec-core)
-flexible and customizable reporting
-extensible expectation language (rspec-expectations)
-built-in mocking/stubbing framework (rspec-mocks)
The post is a brief step by step process to setup RSpec with your project and use it. The tests for rspec will be written in capybara language, which is similar to natural language and is widely used for testing purposes. Capybara helps you test web applications by simulating how a real user would interact with your app.
It is agnostic about the driver running your tests and comes with Rack::Test and Selenium support built in. WebKit is supported through an external gem. (https://rubydoc.info/gems/capybara/2.2.1/frames)
1. Include gem ‘rspec-rails’, gem “capybara”, “~> 2.1.0”, gem “shoulda”, “~> 3.3.2”, gem ‘factory_girl_rails’
In your command line install the rspec gems:
2. gem install rspec
3. gem install rspec-rails
4. gem install capybara
Generate the rspec testing module files using this command
5. script/rails generate rspec:install
Run bundle installer
6. bundle install
Run required migrations
7. rake db:migrate RAILS_ENV=development
8. Include require ‘rspec/rails’, require ‘capybara/rspec’ in you spec_helper file
9. Add config.include Capybara::DSL in the same file to make Capybara available in all test cases deriving from ActionDispatch::IntegrationTest:
Generators
Once the environment is set. Its time to generate the testing files. There are different test types including, model , view, controller,and integeration tests. Other types of tests are request, mailer and helper tests.
They are written as per requirements to cover every aspect of the application. This article will cover commands and examples related to the most commonly used type of Rspec Tests.
-rails generate rspec:model model_name
-rails generate rspec:controller contoller_name
-rails generate rspec:view folder_name view_name
-rails generate integration_test feature_name
Execution Commands
To run specific tests from command line following commands should be used:
Models: bundle exec rspec spec/models/search_phrase_spec.rb
Controllers: bundle exec rspec spec/controllers/users_controller_spec.rb
Views: bundle exec rspec spec/views/search_phrases/index.html.erb_spec.rb
Integration: bundle exec rspec spec/features/search_phrases_spec.rb
The Naming convention for the Rspec Testing files:
Models: modelname_spec.rb
Controllers: controllername_controller_spec.rb
Views: viewname.html.extensionname_spec.rb
Integeration: featurename_spec.rb
Example of Tests
Stub method to simulate dummy user creation and devise sign_in authentication before tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
before :all do @user = User.find_by_name( 'testuser' ) @user ||= begin user = User. new (email: "test@gmail.com" , password: "$2a$10$T" , uid: "10000697585" , name: "testuser" , authority_token: "AUExuiUkFoBAEsYd" ) user.save! user end end before :each do User.stub( :current_user ).and_return( @user ) sign_in @user end |
OR USE FactoryGirl to create the user for model test
1
2
3
4
5
6
7
8
9
10
11
|
FactoryGirl.define do factory :user do |user| user.email "testformodel@gmail.com" user.password "$2a$10$T" user.uid "10000697585" user.name "testuserformodel" user.authority_token "AUExuiUkFoBAEsYd" end end |
You can use either of the above mentioned codes as per need.
Models
Here is an example code for the associated models:
class SearchPhrase
2. # bowling.rb
1
2
3
4
5
6
7
8
|
class Bowling def hit(pins) end def score 0 end end |
Following are the related Rspec tests:
Test
First of all:
1. # search_phrase_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
require 'spec_helper' class ValidatingWidget 2 . # bowling_spec.rb <pre class = "brush: ruby" >require 'bowling' describe Bowling, "#score" do it "returns 0 for all gutter game" do bowling = Bowling. new 20 .times { bowling.hit( 0 ) } bowling.score.should eq( 0 ) end end </pre><p> These tests will pass when executed</p> <h3>Controller</h3> <p>In this example a controller method creates search phrases for a specific user using keywords and saves them in database<br> #search_phrase_controller.rb</p> <pre class = "brush: ruby" > def create @search_phrase = current_user.search_phrases. new (params[ :search_phrase ].permit( :keyword )) if @search_phrase .save redirect_to search_phrases_path, notice: 'Search Phrase was successfully created.' } end end </pre><p> Following are the related Rspec tests:</p> <h3>Test</h3> <p> #search_phrase_controller_spec.rb</p> <pre class = "brush: ruby" >require 'spec_helper' describe SearchPhrasesController do include Devise::TestHelpers describe "POST #create" do it "should pass params to search phrases" do post :create ,search_phrase: {keyword: 'testkeywordnew' } assigns[ :search_phrase ].keyword.should == 'testkeywordnew' end it " should redirect to 'index' with a notice on successful save" do post :create ,search_phrase: {keyword: 'testkeywordnew' } response.should redirect_to(search_phrases_path) end end </pre> <h3>View</h3> <p>This example is shows the index page of the search phrase example app for which the tests are written<br><br> #index.html.erb</p> <pre class = "brush: ruby" ><div> </div> <h2> </h2> <div> <button>Add Keyword</button></div> <br><div> 'new' %> </div> <div> <table><thead><tr><td>Keywords</td> <td>Options</td> </tr></thead><tbody><tr id= "table-row-<%=keyword.id%>" ><td> <div></div> <div> 'edit' , :locals => { :search_phrase => keyword} %> </div> </td> <td> <button id= "button-edit-<%=keyword.id%>" class = "btn btn-info" ><i class = "icon-pencil icon-white" ></i></button> </td> </tr></tbody></table></div> </pre> <h3>Test</h3> <p>Following are the related Rspec tests:</p> <p> #index.html.erb_spec.erb</p> <pre class = "brush: ruby" >require 'spec_helper' describe "search_phrases/index.html.erb" do include Devise::TestHelpers <put user= "" sign= "" in = "" stub= "" here= "" if = "" devise= "" authentication= "" required= "" > #Mock model is creating a virtual model to write view tests let( :search_phrase ) do mock_model( 'SearchPhrase' , :id => 1 , :keyword => 'testkeyword2' , :user_id => @user .id, :created_at => Time .utc( 2000 )) end before do assign :search_phrases , [] end #displays and renders the pages it "renders the template 'index.html.erb_spec'" do puts "*--Search Phrases #{@search_phrases.to_yaml}" render expect(view).to render_template( "search_phrases/index" , "search_phrases/_new" ) end #displays script it "displays the script tag '<script> <!--//--><![ CDATA [// ><!-- <!--//--><![ CDATA [// ><!-- '" do render expect(rendered).to match /<script>/ end it "displays the user image in index" do render expect(rendered).to include( @user .image) end it "displays the user name in index" do render expect(rendered).to include( @user .name) end it "displays the button Text 'Add Keyword'" do render expect(rendered).to match /Add Keyword/ end it "displays the button 'Add Keyword'" do render puts "*Rendered View #{rendered}" rendered.should have_button( 'Add Keyword' ) end it "displays the heading'Keyword'" do render rendered.should have_content( 'Keyword' ) end it "displays the button 'Add Keyword'" do render rendered.should have_button( 'Add' ) end end //--><!]]]]><![ CDATA [> //--><!]]> </script></put></pre><h3>Integration</h3> <p>Integration tests deal with the integration functions of a complete set of application function. For the scope of this post, we are taking example of a page, where user opens a web page, the application checks if user is signed in or not , if user is signed in , the user has authority to perform search function using a keyword phrase, and edit or update the search phrases.</p> <h3>Test</h3> <p>The tests show how these functions are tested:</p> <pre class = "brush: ruby" >describe "SearchPhrases" do #@user stub code for authentication or phrase creation> #===========TESTS START========================================================= # '/' refers to home of application def logged_in? page.has_selector? "username" , text: "Sign Out" end describe "Go to Application Start" do it "should not have user logged in before" do visit '/' logged_in?.should == false end it "should have the content 'Welcome to Demo Application'" do visit '/' expect(page).to have_content( 'Welcome to Demo Application' ) end end describe "check images and icons" do it "should have the Demo logo" do visit '/' puts "*Demo Logo xpath-> #{:xpath.to_s}" page.should have_css( "img[src*='arkhitech']" ) end end describe "After User Logs In" , js: true do before :each do unless logged_in? visit '/' OmniAuth.config.mock_auth[ :provider ] = { provider: 'provider' , uid: @user .uid, credentials: { token: @user .provider_token } } click_link "Sign in" end end it "should be logged in" do logged_in?.should == true end it "should show the name of user" do page.should have_content "#{@user.name}" end it "should show the display picture of user" do page.should have_css( "img[src*='#{@user.image}']" ) end #------------------------------- Now Add Key Word Button Pressed---------------- it "click button 'Add Keyword' and display Keyword heading" do find( '#toggleKeywordAdd' ).click expect(page).to have_content( 'Keyword' ) end it "click button 'Add Keyword' and display edit and remove buttons" do find( '#toggleKeywordAdd' ).click fill_in( 'search_phrase[keyword]' , :with => 'KEYWORD_3' ) click_button( "Add" ) expect(page).to have_content( 'KEYWORD_3' ) page.should have_css( "button[class*='btn btn-info']" ) page.should have_css( "a[class*='btn btn-danger remove_fields']" ) end it "click button 'Add Keyword', adds keyword and click update button to update" do find( :css , "button[id*='button-edit-#{@search_phrase.id}']" ).click page.should have_css( "input[class*='btn btn-medium btn-primary']" ) page.should have_css( "input[id*='search_phrase_keyword']" ) fill_in( 'search_phrase[keyword]' , :with => 'KEYWORD_EDIT' ) click_button( "Update" ) expect(page).to have_content( 'KEYWORD_EDIT' ) end it "log out user on Sign Out" do click_link( "Sign Out" ) current_path.should eq root_path logged_in?.should == false end end end </pre><p> These are some comprehensive examples which I practiced as a beginner. Again practicing brings perfection, so keep practicing with RSpec and get in control of your huge big applications. For your ease, here is quick summary of capybara language commands to write your tests. [courtesy: github/zhengjia]</p> <p><em>=Navigating=</em><br><em> visit( '/projects' )</em><br><em> visit(post_comments_path(post))</em></p> <p><em>=Clicking links and buttons=</em><br><em> click_link( 'id-of-link' )</em><br><em> click_link( 'Link Text' )</em><br><em> click_button( 'Save' )</em><br><em> click( 'Link Text' ) # Click either a link or a button</em><br><em> click('Button Value')</em></p> <p><em>=Interacting with forms=</em><br><em> fill_in( 'First Name' , :with => 'John' )</em><br><em> fill_in( 'Password' , :with => 'Seekrit' )</em><br><em> fill_in( 'Description' , :with => 'Really Long Text…' )</em><br><em> choose( 'A Radio Button' )</em><br><em> check( 'A Checkbox' )</em><br><em> uncheck( 'A Checkbox' )</em><br><em> attach_file( 'Image' , '/path/to/image.jpg' )</em><br><em> select( 'Option' , :from => 'Select Box' )</em></p> <p><em>=scoping=</em><br><em> within( "//li[@id='employee']" ) do </em><br><em> fill_in 'Name' , :with => 'Jimmy' </em><br><em> end </em><br><em> within( :css , "li#employee" ) do </em><br><em> fill_in 'Name' , :with => 'Jimmy' </em><br><em> end </em><br><em> within_fieldset( 'Employee' ) do </em><br><em> fill_in 'Name' , :with => 'Jimmy' </em><br><em> end </em><br><em> within_table( 'Employee' ) do </em><br><em> fill_in 'Name' , :with => 'Jimmy' </em><br><em> end </em></p> <p><em>=Querying=</em><br><em> page.has_xpath?( '//table/tr' )</em><br><em> page.has_css?( 'table tr.foo' )</em><br><em> page.has_content?( 'foo' )</em><br><em> page.should have_xpath( '//table/tr' )</em><br><em> page.should have_css( 'table tr.foo' )</em><br><em> page.should have_content( 'foo' )</em><br><em> page.should have_no_content( 'foo' )</em><br><em> find_field( 'First Name' ).value</em><br><em> find_link( 'Hello' ).visible?</em><br><em> find_button( 'Send' ).click</em><br><em> find( '//table/tr' ).click</em><br><em> locate( "//*[@id='overlay'" ).find( "//h1" ).click</em><br><em> all( 'a' ). each { |a| a[ :href ] }</em></p> <p><em>=Scripting=</em><br><em> result = page.evaluate_script( '4 + 4' );</em></p> <p><em>=Debugging=</em><br><em> save_and_open_page</em></p> <p><em>=Asynchronous JavaScript=</em><br><em> click_link( 'foo' )</em><br><em> click_link( 'bar' )</em><br><em> page.should have_content( 'baz' )</em><br><em> page.should_not have_xpath( '//a' )</em><br><em> page.should have_no_xpath( '//a' )</em></p> <p><em>=XPath and CSS =</em><br><em> within( :css , 'ul li' ) { ... }</em><br><em> find( :css , 'ul li' ).text</em><br><em> locate( :css , 'input#name' ).value</em><br><em> Capybara.default_selector = :css </em><br><em> within( 'ul li' ) { ... }</em><br><em> find( 'ul li' ).text</em><br><em> locate( 'input#name' ).value</em></p> <h3></h3> <h3>Further Reading</h3> <p>Furthur information and best practices can be reviewd on RDocs:</p> <p><a href= "https://rubydoc.info/gems/rspec-core" >https://rubydoc.info/gems/rspec-core</a><br><br><a href= "https://rubydoc.info/gems/rspec-expectations" >https://rubydoc.info/gems/rspec-expectations</a><br><a href= "https://rubydoc.info/gems/rspec-mocks" >https://rubydoc.info/gems/rspec-mocks</a><br><a href= "https://rubydoc.info/gems/rspec-rails" >https://rubydoc.info/gems/rspec-rails</a><br><a href= "https://github.com/rspec/rspec" >https://github.com/rspec/rspec</a><br><a href= "https://github.com/rspec/rspec-rails" >https://github.com/rspec/rspec-rails</a></p> |
Leave a Reply