This is an overview of things I've learnt while working on my side project anonforum in Ruby on Rails.
Today's topics:
- Testing (models and controllers)
- Routing
- Controllers
- Views (helpers)
This is everyone's favourite part of development right?!?! Thankfully testing in Rails is very straight forward.
To run tests you simply run:
rails test {file/directory}
I started with a basic test to determine if the index route returned successfully. To do this I started by generating a controller and test:
rails g controller post --no-assets --no-helper
create app/controllers/post_controller.rb
invoke erb
create app/views/post
invoke test_unit
create test/controllers/post_controller_test.rb
test/controllers/post_controller_test.rb
require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
test "can see threads" do
get '/'
assert_response :success
end
end
Which failed because the route and controller was not setup, so I updated my routes and controller files then added an index view file app/views/post/index.html.erb
.
config/routes.rb
Rails.application.routes.draw do
get '/' => 'post#index'
end
app/controllers/post_controller.rb
class PostController < ApplicationController
def index
render 'index'
end
end
Now we run the test:
rails test test/controllers/post_controller_test.rb
Run options: --seed 59367
# Running:
.
Finished in 0.795857s, 1.2565 runs/s, 1.2565 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Green!
Now let's make sure posts are being passed to the view.
test/controllers/post_controller_test.rb
require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
test "can see threads" do
get '/'
assert_response :success
assert_not_nil assigns(:posts)
end
end
This will fail as all we're currently doing is rendering the view, so let's make all the post avaiable to the view
app/controllers/post_controller.rb
class PostController < ApplicationController
def index
@posts = Post.all
render 'index'
end
end
And let's add some content to our index view:
app/views/post/index.html.erb
<ul>
<% @posts.each do |post| %>
<li>
<h5><a href="#"><%= post.title %></a></h5>
<p><%= post.body %></p>
</li>
<% end %>
</ul>
Re-run the test:
rails test test/controllers/post_controller_test.rb
Run options: --seed 10337
# Running:
.
Finished in 0.756118s, 1.3225 runs/s, 2.6451 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
Green again!
Now we'd like to be able to create new posts (threads), so let's add a test for seeing the new post (thread) form
test/controllers/post_controller_test.rb
require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
test "can see threads" do
get '/'
assert_response :success
assert_not_nil assigns(:posts)
end
test "can see new thread form" do
get '/thread/new'
assert_response :success
end
end
This fails as we have yet to define a route for it and a controller method for it and there is no view file, so let's update those now
config/routes.rb
Rails.application.routes.draw do
get '/' => 'post#index'
get '/thread/new' => 'post#new'
end
app/controllers/post_controller.rb
class PostController < ApplicationController
def index
render 'index'
end
def new
render 'new'
end
end
Create app/views/post/new.html.erb
and add the following
<%= form_tag("/thread") do %>
<input name="title" type="text" />
<textarea name="body"></textarea>
<button type="submit">Submit</button>
<% end %>
I'm sure you noticed the method above form_tag("/thread") do
, this generates some content for us we don't want to be bothered with like CSRF tokens, encoding/charset, route action, and http method. Just ignore the /thread
part for now we'll adding that in later. The output of the above will look like:
<form action="/thread" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="Z3Gor/8T/h0WkynAH/IufRxRlvEBpUc4QN0dIEbnGDQjCCPBLCOI2qXgKyGXQAnGujj14zSeZBiJsi+PaP9jGA==">
<input id="title" name="title" type="text">
<textarea id="body" name="body"></textarea>
<button type="submit">Submit</button>
</form>
Re-run the test:
rails test test/controllers/post_controller_test.rb
Run options: --seed 58678
# Running:
..
Finished in 0.761473s, 2.6265 runs/s, 3.9397 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Green again!
Now that the form is setup we need to make sure the route that creates post (threads) works, here's the setup for the test
test/controllers/post_controller_test.rb
require 'test_helper'
class PostControllerTest < ActionDispatch::IntegrationTest
test "can see threads" do
get '/'
assert_response :success
assert_not_nil assigns(:posts)
end
test "can see new thread form" do
get '/thread/new'
assert_response :success
end
test "can create a thread" do
get "/thread/new"
assert_response :success
post "/thread",
params: { title: "My New Thread", body: "The body of my new thread" }
assert_response :redirect
follow_redirect!
assert_response :success
assert_select "a", "My New Thread"
end
end
This test is a little more involved so we'll take it line by line:
-
test "can create a thread" do
- naming the test
-
get "/thread/new"
- go to the new thread (post) form page
-
post "/thread", params: { title: "My New Thread", body: "The body of my new thread" }
- Do a
POST
action against the route/thread
with the parameters{ title: "My New Thread", body: "The body of my new thread" }
- Do a
-
assert_response :redirect
- Assert the response is a redirect (302)
-
follow_redirect!
- Follow the redirect to make new assertions on the redirected page (which will be
/
in our case)
- Follow the redirect to make new assertions on the redirected page (which will be
-
assert_response :success
- Assert the page redirected to gives us a successful response (200)
-
assert_select "a", "My New Thread"
- Assert an element on the page of
a
(anchor tag) has the contentMy New Thread
- Assert an element on the page of
All this just to check if a post (thread) was created successfully!
Now we know this will fail as we've setup none of the above, so let's update all the necessary files now
config/routes.rb
Rails.application.routes.draw do
get '/' => 'post#index'
get '/thread/new' => 'post#new'
post '/thread' => 'post#create'
end
app/controllers/post_controller.rb
class PostController < ApplicationController
def index
render 'index'
end
def new
render 'new'
end
def create
@post = Post.new(title: params[:title], body: params[:body])
if @post.save
redirect_to action: 'index' and return
else
render 'new'
end
end
end
Now let's try the tests again:
rails test test/controllers/post_controller_test.rb
Run options: --seed 42830
# Running:
...
Finished in 0.775124s, 3.8703 runs/s, 9.0308 assertions/s.
3 runs, 7 assertions, 0 failures, 0 errors, 0 skips
There you have it! I can now create threads (posts) in my application! I ran into a few bumps while trying to figure this out, the documentation isn't very straight forward to navigate but once I found what I was looking for it worked almost flawlessly. Also, there's a few things that were left out like validation error output and the styling of the mark up. If you'd like to see more have a look here https://github.com/patoui/anon-forum.
Feel free to share! Thanks!