2016-11-05 1 views
2

Я следующий тест (который, вероятно, более функционального теста, чем интеграция, но ...):Grails 3 Интеграция Spec имеет Странное поведение транзакционной

@Integration(applicationClass = Application) 
@Rollback 
class ConventionControllerIntegrationSpec extends Specification { 

    RestBuilder rest = new RestBuilder() 
    String url 

    def setup() { 
    url = "http://localhost:${serverPort}/api/admin/organizations/${Organization.first().id}/conventions" 
    } 

    def cleanup() { 
    } 

    void "test update convention"() { 
    given: 
    Convention convention = Convention.first() 

    when: 
    RestResponse response = rest.put("${url}/${convention.id}") { 
     contentType "application/json" 
     json { 
     name = "New Name" 
     } 
    } 

    then: 
    response.status == HttpStatus.OK.value() 
    Convention.findByName("New Name").id == convention.id 
    Convention.findByName("New Name").name == "New Name" 

    } 
} 

Данные загружаются через Bootstrap (который admittadly может быть проблемой), но проблема в том, что я в блоке then; он находит Convention по новому имени и совпадениям id, но при тестировании поля name он терпит неудачу, поскольку он все еще имеет старое имя.

При чтении документации по тестированию я думаю, что проблема заключается в том, на какой сессии создаются данные. Поскольку @Rollback имеет сеанс, который отделен от BootStrap, данные на самом деле не гелеобразуют. Например, если я загружаю данные через блок теста given, тогда эти данные не существуют, когда мой контроллер вызывается RestBuilder.

Вполне возможно, что я не должен делать такой тест таким образом, поэтому предложения ценятся.

ответ

3

Это, безусловно, функциональный тест: вы делаете HTTP-запросы против своего сервера, а не вызываете вызовы методов, а затем делаете утверждения о последствиях этих вызовов.

Вы не можете получить автоматические откаты с функциональными тестами, потому что вызовы выполняются в одном потоке, и они обрабатываются в другом, независимо от того, проходит ли тест в той же JVM, что и сервер, или нет. Код в BootStrap запускается один раз, прежде чем все тесты будут запущены и будут совершены (либо из-за того, что вы внесли изменения в транзакцию, либо через autocommit), а затем тестовый код «клиент» запускается в своем новом сеансе Hibernate и в транзакции, которая инфраструктура тестирования запускается (и откатывается в конце метода теста), а серверный код запускается в своем собственном сеансе Hibernate (из-за OSIV) и в зависимости от того, являются ли ваши контроллеры (ы) и службы (-ы) транзакционный или нет, может работать в другой транзакции или может просто автокомментировать.

Одна вещь, которая, вероятно, не является фактором здесь, но должна всегда учитываться с помощью тестов на сохранение Hibernate, является кэширование сеансов. Сеанс Hibernate - это кеш первого уровня, и динамический искатель, такой как findByName, вероятно, вызовет флеш, который вы хотите, но должен быть явным в тестах. Но он не будет очищать кешированные элементы, и вы рискуете ложными срабатываниями с таким кодом, потому что на самом деле вы не можете загружать новый экземпляр - Hibernate может возвращать кешированный экземпляр. Это определенно будет при вызове get(). Я всегда добавляю метод flushAndClear() к интеграционным и функциональным базовым классам, и я бы поставил его на вызов после вызова put в блоке when, чтобы убедиться, что все сбрасывается из Hibernate в базу данных (не совершено, просто покраснелось) и очистка для принудительной реальной перезагрузки. Просто выберите случайный класс домена и используйте withSession, например.

protected void flushAndClear() { 
    Foo.withSession { session -> 
     session.flush() 
     session.clear() 
    } 
} 

Поскольку put происходит в одном потоке/сеанса/TX и искатели работают в своих собственных, это не должно иметь эффект, но должен быть шаблон используется в целом.

+0

Спасибо, Burt. Это помогает понять некоторые вещи. То, что меня сбивало с толку, было в Grails 2, там была довольно четкая линия между Integration и Functional (поскольку в то время функциональные тесты не были гражданами первого класса), а в Grails 3, создание функционального теста использует '@ Integration' и' @Rollback 'в шаблоне для GebSpec. – Gregg

+2

Функциональные тесты в основном состоят из граждан первого сорта, но для создания тестовой/функциональной папки это зависит от плагина.Вначале это определенно сбивает с толку, что в Grails 3 интеграционные и функциональные тесты используют одну и ту же папку и что веб-сервер запускается для тестов интеграции, что еще больше размывает различия. –

+0

Вау, это объясняет, почему я не мог получить откаты, работающие в прошлые дни. Я понял, что что-то происходит по этим линиям. Большое вам спасибо за разъяснение этого. Существуют ли какие-либо рекомендации по настройке и откату данных функциональных (API) тестовых данных? В настоящее время я настраиваю свои данные через BootStrap, как и Gregg, и отменяет изменения в блоках очистки через вызовы API после прохождения теста. –

Смежные вопросы