One of the advantages of views as objects is that we can unit test them. We can both understand if a specific presentational logic behaves correctly and/or assert the contents of the rendered markup.
For the following example we’re gonna use RSpec for the concise syntax for test doubles.
# spec/web/views/books/show_spec.rb
require_relative '../../../../apps/web/views/books/show'
RSpec.describe Web::Views::Books::Show do
let(:exposures) { Hash[book: double('book', price: 1.00), current_user: user, params: {}] }
let(:template) { Hanami::View::Template.new('apps/web/templates/books/show.html.erb') }
let(:view) { Web::Views::Home::Another.new(template, exposures) }
let(:rendered) { view.render }
let(:user) { double('user', admin?: false) }
context "price" do
it "returns formatted price" do
expect(view.formatted_price).to eq("$1.00")
end
end
context "edit link" do
it "doesn't show it by default" do
expect(rendered).to_not match(%(<a href="">edit</a>))
end
context "when admin" do
let(:user) { double('user', admin?: true) }
it "shows it" do
expect(rendered).to match(%(<a href="">edit</a>))
end
end
end
end
The first part of the test code above is about book’s formatting price. This presentational logic is verified by asserting the returning value of view.formatted_price
.
The remaining code is about permissions related logic: the edit link must be rendered only if the current user is an admin. This is tested by looking at the output of the template.
Asserting presentational logic directly via view’s methods, or indirectly via rendered markup are two EQUIVALENT ways.
Notice that exposures
includes an unused params
key. While this is not strictly required, we recommend providing it since it’s expected by some standard view helpers (e.g. form helpers).
Let’s have a look at the corresponding production code.
# apps/web/views/books/show.rb
module Web
module Views
module Books
class Show
include Web::View
def formatted_price
"$#{ format_number book.price }"
end
def edit_link
if can_edit_book?
link_to "Edit", routes.edit_book_path(id: book.id)
end
end
private
def can_edit_book?
current_user.admin?
end
end
end
end
end