Skip to content

Instantly share code, notes, and snippets.

@paulcsmith
Last active November 21, 2017 21:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulcsmith/c5da6e8888d1a50f13fb6d9acc37a6a9 to your computer and use it in GitHub Desktop.
Save paulcsmith/c5da6e8888d1a50f13fb6d9acc37a6a9 to your computer and use it in GitHub Desktop.
Testing Lucky apps
# spec/support/app_conn.cr
require "http/client"
class AppConn
getter! response
@response : HTTP::Client::Response? = nil
module Matchers
def contain_html(html : String)
ContainHtmlExpectation.new(html)
end
def redirect_to(path : String)
RedirectedToExpectation.new(path)
end
end
struct RedirectedToExpectation
def initialize(@expected_path : String)
end
def match(conn)
conn.response.status_code == 302 &&
conn.response.headers.fetch("Location") == @expected_path
end
def failure_message(conn)
"Expected to redirect to #{@expected_path}, but redirected to #{conn.response.headers.fetch("Location")}"
end
def negative_failure_message(conn)
"Expected not to redirect to #{@expected_path}, but redirected to #{conn.response.headers.fetch("Location")}"
end
end
struct ContainHtmlExpectation
def initialize(@expected_html : String)
end
def match(conn)
conn.response.status_code == 200 &&
HTML.unescape(conn.response.body).includes? @expected_html
end
def failure_message(conn)
<<-TEXT
Expected response to have status 200 with html: #{@expected_html}
Instead got:
Status: #{conn.response.status_code}
HTML: #{conn.response.body}
TEXT
end
def negative_failure_message(conn)
<<-TEXT
Expected not to have response with status 200 and html: #{@expected_html}
TEXT
end
end
def visit(path)
request = HTTP::Request.new("GET", path)
@response = process_request request
end
private def process_request(request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
middlewares.call context
response.close
io.rewind
HTTP::Client::Response.from_io(io, decompress: false)
end
private def middlewares
HTTP::Server.build_middleware AppHandlers::HANDLERS
end
end
# These are all the handlers you'd find in src/server.cr
# This is out of date. Use the handlers defined in your src/server.cr
# Remember to require this in app.cr
# This could go in src/app_handlers.cr
class AppHandlers
HANDLERS = [
HTTP::ErrorHandler.new,
HTTP::LogHandler.new,
LuckyWeb::RouteHandler.new,
HTTP::StaticFileHandler.new("./public", false),
]
end
# This allows you to use Forms as factories. Kind of like FactoryGirl
abstract class BaseBox
getter form
macro form(form_class)
@form = {{ form_class }}.new
end
macro inherited
form {{ @type.name.gsub(/Box/, "::BaseForm").id }}
end
macro method_missing(call)
form._{{ call.name }}.value = {{ call.args.first }}
self
end
def save
form.save
end
def self.save_pair
new.save
new.save
end
end
# spec/support/database_cleaner.cr
class DatabaseCleaner
def self.truncate
PostgreSQLAdapter.new.truncate
end
end
class PostgreSQLAdapter
def cascade
"CASCADE"
end
def restart_identity
"RESTART IDENTITY"
end
def truncate
return if table_names.empty?
statement = ("TRUNCATE TABLE #{table_names.map { |name| name }.join(", ")} #{restart_identity} #{cascade};")
LuckyRecord::Repo.run do |db|
db.exec statement
end
end
def table_names
tables_with_schema(excluding: "schema_migrations")
end
def tables_with_schema(excluding : String)
select_rows <<-SQL
SELECT table_name
FROM information_schema.tables
WHERE table_schema='public'
AND table_type='BASE TABLE'
AND table_name != '#{excluding}';
SQL
end
def select_rows(statement)
rows = [] of String
LuckyRecord::Repo.run do |db|
db.query statement do |rs|
rs.each do
rows << rs.read(String)
end
end
end
rows
end
end
require "../../../spec_helper"
describe Admin::Users::Index do
it "shows a list of users" do
users = UserBox.save_pair
conn = AppConn.new
conn.visit Admin::Users::Index.path
UserQuery.new.each do |user|
conn.should contain_html(user.email)
end
end
end
# or
require "../../spec_helper"
describe Home::Index do
it "redirects to the landing page" do
conn = AppConn.new
conn.visit "/"
conn.should redirect_to(LandingPage::Index.path)
end
end
require "./app"
require "colorize"
server = HTTP::Server.new("0.0.0.0", 8080, AppHandlers::HANDLERS)
puts "Listening on #{ServerSettings.url}...".colorize(:green)
server.listen
require "spec"
require "../src/app"
require "./support/database_cleaner"
require "./support/**"
# This is important
include AppConn::Matchers
Spec.before_each do
DatabaseCleaner.truncate
end
class UserBox < BaseBox
def initialize
email "paul"
name "paul@thoughtbot.com"
encrypted_password "fake_password"
end
def yoda
name "yoda"
email "yoda@starwars.com"
end
def set_name(value)
name value
email "#{value}@thoughtbot.com"
end
end
# You could add custom methods too
UserBox.new.yoda.save
# or
UserBox.new.set_name("Rey").save
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment