Запуск нескольких тестов в рамках одного 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) {
...
}
}
}