garnet-spec
garnet-spec copied to clipboard
Add helper methods to ease controller testing
Right know is possible to test controller actions preloading routes of your application and borrowing a helper method from Amber's source.
For example, for making an HTTP GET request we could do the following:
def get(path)
handler = Amber::Server.instance.handler
handler.prepare_pipelines # Preload routes
request = HTTP::Request.new("GET", path) # Create request
create_request_and_return_io(handler, request) # Generate response
end
With that a user could test its route as this:
response = get("/my_path")
response.status_code.should eq 200
It would be very useful to have a method for each HTTP verb to make requests and be able to attach params and headers as well.
@epergo thanks for opening this issue.
So Garnet already has something like this. It might look a little cumbersome but you can simply do this to test your Controller
class BlogControllerTest < GarnetSpec::Controller::Test
getter handler : Amber::Pipe::Pipeline
def initialize
@handler = Amber::Server.instance.handler
@handler.prepare_pipelines
end
end
Then write your unit tests as
describe BlogControllerTest do
subject = BlogControllerTest.new
it "renders blog index template" do
Blog.clear
response = subject.get "/blogs"
response.status_code.should eq(200)
response.body.should contain("Blogs")
end
end
Let me know if this is helpful
@eliasjpr it seems like the class BlogControllerTest part could be automated, no?
Hi, For my project what I have done is the following:
macro define_request_methods(handler)
{{handler}}.prepare_pipelines
{% http_read_verbs = %w(get head) %}
{% http_write_verbs = %w(post put patch delete) %}
{% http_verbs = http_read_verbs + http_write_verbs %}
{% for method in http_verbs %}
def {{method.id}}(path, headers : HTTP::Headers? = nil, body : String? = nil)
request = HTTP::Request.new("{{method.id}}".upcase, path, headers, body)
{% if http_write_verbs.includes?(method) %}
request.headers["Content-Type"] = "application/json"
{% end %}
process_request({{ handler }}, request)
end
{% end %}
def process_request(router, request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
router.call(context)
response.close
io.rewind
HTTP::Client::Response.from_io(io, decompress: false)
end
end
define_request_methods(Amber::Server.handler)
I have mixed Garnet code generation for http verbs methods and the routes of my application. So now I can do this:
require "../../../spec_helper"
describe(V1::AwesomeController) do
describe("index") do
after { clean }
describe("when there are resources") do
it "should return all" do
(0..2).each { |i| create_resources(i) }
response = get("/api/v1/resources")
parsed_body = JSON.parse(response.body)
expect(parsed_body["data"].size).must_equal(Resource.all.size)
expect(response.status_code).must_equal(200)
end
end
end
end
So I don't have to create a Mock Controller to test
@robacarp is a little tricky since we do not have any Amber reference in Garnet. So we would have to somehow define the @handler type in spec_helper.cr so garnet can read from it, it seems doable.
@epergo the prepare_pipelines is very specific to Amber and it adds coupling to the Amber Framework. We can definitely take advantage of your approach. Maybe something like
{{handler}}.prepare_pipelines if {{handler}}.responds_to? :prepare_pipelines
Also I personally prefer the inheritance approach since it keeps the request methods withing the scope of the test and not mixed other methods.
I think this would work. WDYT?
Good points @eliasjpr , I didn't think about coupling with Amber because we were in a repository created under the scope of Amber, but you are right!
About inheritance, could it be where included? And only include that set of methods in test files where you actually need it, for example:
DISCLAIMER I don't know if this is possible, just an idea/dream
require "../../../spec_helper"
describe(V1::AwesomeController) do
include RequestHelpers
describe("index") do
after { clean }
describe("when there are resources") do
it "should return all" do
(0..2).each { |i| create_resources(i) }
response = get("/api/v1/resources")
parsed_body = JSON.parse(response.body)
expect(parsed_body["data"].size).must_equal(Resource.all.size)
expect(response.status_code).must_equal(200)
end
end
end
end
I'm having trouble getting started with Amber, my tests aren't compiling and can't seem to find this class GarnetSpec::Controller::Test
crystal spec --error-trace ✘ 1
error in line 3
Error: while requiring "./spec/controllers/account_controller_spec.cr"
In spec/controllers/account_controller_spec.cr:22:31
22 | class AccountControllerTest < GarnetSpec::Controller::Test
^---------------------------
Error: undefined constant GarnetSpec::Controller::Test
How are we supposed to write controller tests?