Как запустить ScalaTest с Guice DI и Slick?

Я не знаю, как настроить GuiceApplicationBuilder таким образом, чтобы я мог загружать контроллеры, требующие внедрения DatabaseConfigProvider.

Я хотел бы указать альтернативную базу данных postgres для тестирования или базу данных в памяти (если это возможно).

Код

class   User
extends MySpecs
with    OneAppPerTest
{
    override def newAppForTest( testData: TestData ) = new GuiceApplicationBuilder()
        // Somehow bind a database here, I guess?
        .build()

    "A test" should "test" in
    {
        val result = Application.instanceCache[api.controller.User]
            .apply( app )
            .list()( FakeRequest() )

        ...
    }
}

Трассировка стека

[info] - should return an entity *** FAILED ***
[info]   com.google.inject.ConfigurationException: Guice configuration errors:
[info] 
[info] 1) No implementation for play.api.db.slick.DatabaseConfigProvider was bound.
[info]   while locating play.api.db.slick.DatabaseConfigProvider
[info]     for parameter 1 at api.controller.User.<init>(User.scala:22)
[info]   while locating api.controller.User
[info] 
[info] 1 error
[info]   at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
[info]   at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
[info]   at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info]   at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[info]   at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[info]   at play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[info]   at play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[info]   at play.utils.InlineCache.fresh(InlineCache.scala:69)
[info]   at play.utils.InlineCache.apply(InlineCache.scala:55)
[info]   ...

person Taig    schedule 31.07.2015    source источник


Ответы (2)


Вам нужно добавить конфигурацию в ваш GuiceApplicationBuilder(), после чего все должно автоматически обрабатываться игровым фреймворком. Что-то вроде этого должно помочь:

val app = new GuiceApplicationBuilder()
        .configure(
            Configuration.from(
                Map(
                    "slick.dbs.YOURDBNAME.driver" -> "slick.driver.H2Driver$",
                    "slick.dbs.YOURDBNAME.db.driver" -> "org.h2.Driver",
                    "slick.dbs.YOURDBNAME.db.url" -> "jdbc:h2:mem:",

                    "slick.dbs.default.driver" -> "slick.driver.MySQLDriver$",
                    "slick.dbs.default.db.driver" -> "com.mysql.jdbc.Driver"
                )
            )
        )
        .in(Mode.Test)
        .build()
person Mateusz Dymczyk    schedule 13.08.2015

В этом подходе есть небольшая настройка, но конечный результат справедлив. Прежде всего, начните с реализации собственного GuiceApplicationLoader путем его расширения. См. мой ответ, как это реализовать. Зачем нужен собственный загрузчик приложений? Вы можете указать разные конфигурации/модули для Prod/Dev/Test режимов, а также разные источники данных. У вашего основного application.conf не будет настроен источник данных. Вместо этого вы переместите его в конфигурации, специфичные для среды, которые в любом случае будут объединены с основной конфигурацией загрузчиком приложения. Ваш dev.conf будет выглядеть следующим образом:

slick.dbs {
  default {
    driver = "slick.driver.PostgresDriver$",
    db {
      driver = "org.postgresql.Driver",
      url = "jdbc:postgresql://localhost:5432/dev",
      user = "postgres"
      password = "postgres"
    }
  }
}

Хитрость заключается в том, чтобы использовать одно и то же имя источника данных, в данном случае default, для всех остальных конфигураций (URL-адрес базы данных, драйверы, учетные данные и т. д. будут другими). При такой настройке ваш evolutions будет применяться к вашей тестовой базе данных и базе данных разработки. Ваш test.conf может выглядеть следующим образом:

slick.dbs {
  default {
    // in memory configuration
  }
}

Вместо этого в своих тестах используйте WithApplicationLoader с вашим пользовательским загрузчиком приложений, и все.

@RunWith(classOf[JUnitRunner])
class ApplicationSpec extends Specification {

    "Application" should {

        "return text/html ok for home" in new WithApplicationLoader(new CustomApplicationLoader) {
          val home = route(FakeRequest(routes.ApplicationController.home())).get
          status(home) must equalTo(OK)
          contentType(home) must beSome.which(_ == "text/html")
        }

    }

}

В самом тесте у вас есть доступ к значению app: Application:

val service = app.injector.instanceOf(classOf[IService])
person Mon Calamari    schedule 18.08.2015
comment
как тогда отделить тестовый код от кода продукта? Например, что, если я хочу иметь несколько фиктивных классов в TestModule? Поскольку ваш CustomApplicationLoader будет в рабочем коде, не будет ли это проблемой? - person Mateusz Dymczyk; 18.08.2015
comment
CustomApplicationLoader будет работать в режиме Test в тестах с установленными test.conf и TestModule. - person Mon Calamari; 18.08.2015
comment
Я знаю об этом, но вы используете new TestModule() в своем CustomApplicationLoader, что означает, что все классы, которые используются в TestModule, должны быть доступны из производственного кода, многие люди (включая меня) не любят смешивать тест и прод. код. - person Mateusz Dymczyk; 18.08.2015
comment
Я ввожу свои зависимости по трейту (интерфейсу), и у меня есть абстрактный базовый модуль из всех трех расширений модулей, где я связываю общие реализации. поэтому CommonModule содержит общие реализации, TestModule специфичные для теста и ProdModule специфичные для продукта. - person Mon Calamari; 18.08.2015
comment
Да, но если TestModule содержит все тестовые только классы, а вы выполняете new TestModule() в CustomApplicationLoader, который находится в вашем src, то вы также должны поместить все тестовые классы, которые используются TestModule, в папки src, а не test, верно? Тогда должна быть связь между папками test и src, или я что-то упустил? - person Mateusz Dymczyk; 18.08.2015
comment
Теперь я понимаю вашу точку зрения, в основном вам не нравится поставлять код продукта вместе с тестовым кодом, верно? - person Mon Calamari; 18.08.2015
comment
да, именно так, я стараюсь как можно больше отделить код продукта и теста, если нет веской причины не делать этого (или нет другого пути) - person Mateusz Dymczyk; 18.08.2015
comment
Ничто не запрещает вам реализовать TestApplicationLoader, самостоятельно подключить зависимости и хранить все классы и файлы в каталоге test. GuiceApplicationLoader предназначен для расширения. - person Mon Calamari; 18.08.2015
comment
Верно, но тогда я могу просто использовать GuiceApplicationBuilder, все эти загрузчики кажутся немного накладными для такой простой вещи - person Mateusz Dymczyk; 18.08.2015
comment
Суть в том, чтобы централизовать тестовую конфигурацию и тестовые зависимости. - person Mon Calamari; 18.08.2015
comment
да, но я хочу сказать, что если мы разделим их на 2 загрузчика, то нам это действительно нужно? Создание трейта с newAppForTest и использование GuiceApplicationBuilder всегда сэкономит нам несколько строк кода и даст тот же результат, не так ли? Это то, что я ищу - причина для реализации GuiceApplicationLoader. Было бы разумно, если бы мы сделали это, как в вашем другом ответе, со всеми режимами в одном месте, но, как я указал выше, некоторые могут счесть это плохой практикой. - person Mateusz Dymczyk; 18.08.2015
comment
Если вы беспокоитесь о том, что тестовый и рабочий исходный код перемешаются, просто исключите его из упаковки. stackoverflow.com/questions/20491505/ - person Mon Calamari; 19.08.2015