Как упоминалось ранее в этой главе, для решения этой проблемы мы можем использовать некоторые обратные вызовы модуля Crystal Spec. Давайте начнем с добавления следующего кода в файл spec/spec_helper.cr:
DATABASE = DB.open ENV["DATABASE_URL"]
Spec.before_suite do
DATABASE.exec File.read "#{__DIR__}/../db/000_setup.sql"
DATABASE.exec "ALTER DATABASE \"postgres\" SET
SEARCH_PATH TO \"test\";"
DATABASE.exec File.read "#{__DIR__}/../db/001_articles.sql"
end
Spec.after_suite do
DATABASE.exec "ALTER DATABASE \"postgres\"
SET SEARCH_PATH TO \"public\";"
DATABASE.close
end
Spec._each do
end
Здесь мы создаем константу для представления пула соединений с нашей базой данных. Затем мы определяем обратный вызов, который запускается один раз перед выполнением любого теста. В рамках этого обратного вызова мы запускаем файлы миграции базы данных, чтобы убедиться в наличии схемы и таблиц перед запуском тестов. Мы также выполняем запрос, чтобы гарантировать, что наши таблицы/запросы будут выполняться в соответствии с нашей тестовой схемой. Наконец, у нас есть еще один обратный вызов, который запускается после выполнения всех тестов, чтобы немного подчистить путем сброса пути поиска обратно к общедоступной схеме и закрытия пула соединений.
Теперь, когда у нас есть таблицы для хранения наших данных, нам нужно выполнить очистку, и мы уже определили, где мы будем это делать. Обновите блок Spec.before_each, чтобы он выглядел следующим образом:
Spec.before_each do
DATABASE.exec "TRUNCATE TABLE \"articles\" RESTART IDENTITY;"
end
Здесь мы удаляем все статьи, которые могли быть созданы в рамках каждого интеграционного теста. Сделав это здесь, мы можем гарантировать, что наши тесты не будут мешать друг другу.
На этом этапе, если бы мы снова запустили спецификации, мы бы получили ответ об ошибке 404, поскольку мы не делали ничего, связанного с сохранением каких-либо настроек статьи. Давайте сделаем это дальше.
Чтобы сохранить целенаправленность и простоту, мы просто собираемся выполнять вставки необработанного SQL для целей этой главы. Не стесняйтесь определять некоторые абстракции и вспомогательные методы, а также использовать стороннюю библиотеку приборов — или что-то еще — если хотите.
Поскольку мы автоматически очищаем нашу таблицу после каждого тестового примера, мы можем свободно вставлять любые данные, которые требуются для нашего конкретного тестового примера. В нашем случае нам нужно вставить статью с идентификатором 10. Нам также следует сделать некоторые утверждения против ответа, чтобы убедиться, что это то, что мы ожидаем. Обновите наш тест статьи GET, чтобы он выглядел так:
def test_get_article : Nil
DATABASE.exec <<-SQL
INSERT INTO "articles" (id, title, body, created_at,
updated_at) OVERRIDING SYSTEM VALUE
VALUES (10, 'TITLE', 'BODY', timezone('utc', now()),
timezone('utc', now()));
SQL
response = self.get "/article/10"
response.status.should eq HTTP::Status::OK
article = JSON.parse response.body
article["title"].as_s.should eq "TITLE"
article["body"].as_s.should eq "BODY"
end
Поскольку в наших таблицах для первичного ключа (PK) используется GENERATED ALWAYS AS IDENTITY, нам необходимо включить OVERRIDING SYSTEM VALUE в наши инструкции INSERT, чтобы мы могли указать нужный идентификатор.
В нашем тесте статьи GET мы утверждаем, что запрос прошел успешно и возвращает ожидаемые данные. Мы также можем протестировать поток языка гипертекстовой разметки (HTML), установив заголовок принятия как часть запроса. Давайте определим для этого еще один тестовый пример:
def test_get_article_html : Nil
DATABASE.exec <<-SQL
INSERT INTO "articles" (id, title, body, created_at,
updated_at) OVERRIDING SYSTEM VALUE
VALUES (10, 'TITLE', 'BODY', timezone('utc', now()),
timezone('utc', now()));
SQL
response = self.get "/article/10", headers: HTTP::Headers
{"accept" => "text/html"}
response.status.should eq HTTP::Status::OK
response.body.should contain "<p>BODY</p>"
end
Мы также могли бы легко протестировать создание статьи, например:
def test_post_article : Nil
response = self.post "/article", body: %({"title":"TITLE",
"body":"BODY"})
article = JSON.parse response.body
article["title"].as_s.should eq "TITLE"
article["body"].as_s.should eq "BODY"
article["created_at"].as_s?.should_not be_nil
article["id"].raw.should be_a Int64
end
Независимо от того, как вы это сделаете, в конечном итоге наши интеграционные тесты контроллера статьи оказались довольно простыми и мощными. Они предоставляют средства для тестирования всего потока запроса, включая прослушиватели, преобразователи параметров и обработчики форматов. Он также позволяет тестировать любую пользовательскую логику сериализации или проверки как часть полезной нагрузки запроса/ответа.