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?