Запуск нескольких тестов в рамках одного FakeApplication() в игре 2.0 scala

Я пытаюсь изучить модульные тесты в Play scala, но я сталкиваюсь с некоторыми проблемами. Я пытаюсь выполнить несколько тестов на моем слое моделей следующим образом:

"User Model" should {
    "be created and retrieved by username" in {
        running(FakeApplication()) {
            val newUser = User(username = "weezybizzle",password = "password")
            User.save(newUser)
            User.findOneByUsername("weezybizzle") must beSome
        }
    }
    "another test" in {
        running(FakeApplication()) {
            // more tests involving adding and removing users
        }
    }
}

Однако, когда я так поступаю, я не могу подключиться к базе данных на втором unit test, заявив, что соединение закрыто. Я попытался решить эту проблему, включив весь код в блок, который работает на одном и том же поддельном приложении, но это тоже не сработало.

  running(FakeApplication()) {
    "be created and retrieved by username" in {
        val newUser = User(username = "weezybizzle",password = "password")
        User.save(newUser)
        User.findOneByUsername("weezybizzle") must beSome
    }
    "another test" in {
        // more tests involving adding and removing users
    }
  }

Ответы

Ответ 1

Тесты specs2 выполняются по умолчанию параллельно, что может вызвать проблемы с доступом к базам данных, особенно если вы полагаетесь на содержимое db, предоставленное предыдущим тестом. Поэтому для принудительного последовательного тестирования вы должны указать specs2:

class ModelSpec extends Specification with Logging {
  override def is = args(sequential = true) ^ super.is
...
}

Для тестов, выполненных в одном FakeApplication, вы можете обернуть в нем все тесты:

  running(FakeApp) {
    log.trace("Project tests.")
    val Some(project) = Project.findByName("test1")

    "Project" should {

      "be retrieved by name" in {
        project must beAnInstanceOf[Project]
        project.description must endWith("project")
      }

Весь образец можно найти здесь. Это была моя первая попытка решить проблемы при тестировании MongoDB с Play! рамки.

Второй подход, который я использовал в проекте salat, который, кстати, очень хороший источник примеров specs, касающихся MongoDB (хотя это не приложение Play! framework). Вы должны определить черту, расширяющую Around и Scope, где вы можете поместить все, что вам нужно, для инициализации в экземпляре приложения:

import org.specs2.mutable._
import org.specs2.execute.StandardResults

import play.api.mvc._
import play.api.mvc.Results
import play.api.test._
import play.api.test.Helpers._

trait FakeApp extends Around with org.specs2.specification.Scope {

  val appCfg = Map(
    "first.config.key" -> "a_value",
    "second.config.key" -> "another value"
  )

  object FakeApp extends FakeApplication(
      additionalPlugins = Seq("com.github.rajish.deadrope.DeadropePlugin"),
      additionalConfiguration = appCfg
    ) {
    // override val routes = Some(Routes)
  }

  def around[T <% org.specs2.execute.Result](test: => T) = running(FakeApp) {
    Logger.debug("Running test ==================================")
    test  // run tests inside a fake application
  }
}

Редактировать 2013-06-30:

В текущей версии specs2 подпись Around должна быть:

def around[T : AsResult](test: => T): Result

Конец редактирования

Затем можно написать такой тест:

class SomeSpec extends Specification { sequential // according to @Eric comment

  "A test group" should {
    "pass some tests" in new FakeApp {
      1 must_== 1
    }

    "and these sub-tests too" in {
      "first subtest" in new FakeApp {
         success
      }
      "second subtest" in new FakeApp {
         failure
      }
    }
  }
}

Полный образец такого пакета можно найти здесь.

В заключительном примечании: также полезно очистить тестовую базу данных перед запуском пакета:

  step {
    MongoConnection().dropDatabase("test_db")
  }

Ответ 2

При выполнении тестирования интеграции/запуска тестовых наборов мы столкнулись с такими ожиданиями, как "CacheManager был отключен, он больше не может использоваться" или "SQLException: попытка получить соединение из пула, который уже был завершен", Все они были связаны с перезагрузкой приложения после каждого теста. Мы, наконец, сделали довольно простой признак, который будет проверять работу FakeApplication перед каждым тестом и при необходимости запускать только один.

trait SingleInstance extends BeforeExample {
    def before() {
        if (Play.unsafeApplication == null) Play.start(AppWithTestDb)
    }
}

object AppWithTestDb extends FakeApplication(additionalConfiguration = 
    Map("db.default.url" -> "jdbc:mysql://localhost/test_db")
)

И затем в тесте:

class SampleSpec extends PlaySpecification with SingleInstance {
    "do something" should {
        "result in something" in {
        }
    }
}

Это будет работать для Play 2.3, а также для Play 2.4

Ответ 3

Несколько более чистый подход

import play.api.test._

trait ServerSpec {

  implicit val app: FakeApplication = FakeApplication()
  implicit def port: Port = Helpers.testServerPort

  val server = TestServer(port, app)
}

И затем используйте его с

class UsersSpec extends PlaySpecification with Results with ServerSpec {

  "Users Controller" should {

    step(server.start())

    "get users" in {
      val result = Users.query().apply(FakeRequest())

      val json = contentAsJson(result)
      val stat = status(result)

      stat mustEqual 200
    }

    step(server.stop())
  }
}

Ответ 4

Чтобы проверить свой код на базе базы данных, в случае, если вы тестируете его с помощью входящего в память, вы должны указать его в вызове running:

FakeApplication(additionalConfiguration = inMemoryDatabase())

В определенной степени это заставит вашу базу данных запускать и останавливать выполнение внутреннего блока (будь то одиночное или составленное)

ИЗМЕНИТЬ

Из-за комментария, говорящего, что вы используете mongodb, я бы рекомендовал вам прочитать это blog, в котором я говоря о небольшом плагине, который я написал, чтобы сервер mongodb запустился как встроенный.

Что мы будем делать (включив плагин), чтобы запустить и остановить mongodb в одно и то же время приложения.

Это может помочь вам...

Однако в отношении начального вопроса проблема не должна возникать из запуска или FakeApplication, если только Play-Salat или любой другой связанный с ним плагин не выполняет плохие соединения или кеширование или...

Ответ 5

Такая проблема параллельного тестирования возникает при использовании запущенного метода во многих случаях. Но это уже зафиксировано в play2.1. Здесь как исправить. Если вы хотите использовать это в play2.0.x, вы должны сделать следующее:

trait TestUtil {
  /**
   * Executes a block of code in a running application.
   */
  def running[T](fakeApp: FakeApplication)(block: => T): T = {
     synchronized {
      try {
        Play.start(fakeApp)
        block
      } finally {
        Play.stop()
        play.core.Invoker.system.shutdown()
        play.core.Invoker.uninit()
      }
    }
  }

  /**
   * Executes a block of code in a running server.
   */
  def running[T](testServer: TestServer)(block: => T): T = {
    synchronized {
      try {
        testServer.start()
        block
      } finally {
        testServer.stop()
        play.core.Invoker.system.shutdown()
        play.core.Invoker.uninit()
      }
    }
  }
}

И вы можете использовать следующее:

class ModelSpec extends Specification with TestUtil {
    "User Model" should {
        "be created and retrieved by username" in {
            running(FakeApplication()) {
                val newUser = User(username = "weezybizzle",password = "password")
                User.save(newUser)
                User.findOneByUsername("weezybizzle") must beSome
            }
        }
    }
    ....

Ответ 6

Лучший способ найти один тестовый класс FakeApplication на Scala, следующий пример ниже. Обратите внимание на метод "старт":

@RunWith(classOf[JUnitRunner])
class ContaControllerSpec extends MockServices {

    object contaController extends ContaController with MockAtividadeService with MockAccountService with MockPessoaService with MockTelefoneService with MockEmailService{
        pessoaService.update(PessoaFake.id.get, PessoaFake) returns PessoaFake.id.get
    }

    step(Play.start(new FakeAppContext))

    "ContaController [Perfil]" should {

      "atualizar os dados do usuario logado e retornar status '200' (OK)" in {
          val response = contaController.savePerfil()(FakeRequest(POST, "/contas/perfil").withFormUrlEncodedBody(
              ("nome", "nome teste"), ("sobrenome", "sobrenome teste"), ("dataNascimento", "1986-09-12"), ("sexo", "M")).withLoggedIn(config)(uuid))

              status(response) must be equalTo(OK)
        }

        "atualizar os dados do usuario logado enviando o form sem preenchimento e retornar status '400' (BAD_REQUEST)" in {
            val response = contaController.savePerfil()(FakeRequest(POST, "/contas/perfil").withLoggedIn(config)(uuid))
            status(response) must be equalTo(BAD_REQUEST)
        }
    }

    step(Play.stop)
}

Ответ 7

Принятый ответ мне не помог. Я нахожусь в игре 2.2.3 scala 2.10.3. Это помогло мне.

Может быть, это может помочь.

Расширить BoneCPPlugin

class NewBoneCPPlugin(val app: play.api.Application) extends BoneCPPlugin(app) {


  override def onStop() {
    //don't stop the BoneCPPlugin
    //plugin.onStop()
  }
}

И в вашем тесте test должен быть

    class UserControllerSpec extends mutable.Specification with Logging with Mockito {

    val fakeApp = FakeApplication(additionalConfiguration = testDb,withoutPlugins = Seq("play.api.db.BoneCPPlugin"),
                                  additionalPlugins = Seq("NewBoneCPPlugin"))
    "Create action in UserController " should {
            "return 400 status if request body does not contain user json " in new WithApplication(fakeApp) {
        ...
    }
  }
}