【问题标题】:running multiple tests within the same FakeApplication() in play 2.0 scala在 play 2.0 scala 中在同一个 FakeApplication() 中运行多个测试
【发布时间】:2012-08-15 05:25:20
【问题描述】:

我正在尝试学习 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
        }
    }
}

但是这样做时,我在第二个单元测试中连接数据库失败,说连接已关闭。我试图通过将所有代码封装在一个运行在同一个假应用程序上的块中来解决这个问题,但这也不起作用。

  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
    }
  }

【问题讨论】:

    标签: unit-testing scala playframework-2.0 specs2


    【解决方案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")
          }
    

    可以在here 找到整个样本。这是我在使用 Play 测试 MongoDB 时第一次尝试处理问题!框架。

    我从salat 项目中借用的第二种方法,顺便说一下,这是处理 MongoDB 的规范示例的一个很好的来源(尽管它不是一个 Play! 框架应用程序)。您必须定义一个扩展AroundScope 的特征,您可以在其中将需要初始化的任何内容放入应用程序实例中:

    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
          }
        }
      }
    }
    

    可以在here找到此类套件的完整示例。

    最后一点:在启动套件之前清理测试数据库也很好:

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

    【讨论】:

    • 注意不要写成:"class SomeSpec extends Specification { override def is = args(sequential = true) ^ super.is",你可以写成:"class SomeSpec extends Specification {equential"跨度>
    • @Eric 谢谢!我在文档中没有找到。它是最近的功能吗?
    • 其实我好像还是有问题,我在这里贴了堆栈跟踪stackoverflow.com/questions/12170009/…
    【解决方案2】:

    在进行集成测试/运行测试套件时,我们遇到了诸如“CacheManager 已关闭。无法再使用”或“SQLException:尝试从已关闭的池中获取连接”之类的期望.它们都与每次测试后重新启动应用程序有关。 我们最后做了一个相当简单的 trait,它会在每次测试之前检查一个正在运行的 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())
        }
      }
      

      【讨论】:

      • 这很有帮助!我使用了更简单的“running(TestServer(3333, FakeApplication()))”
      【解决方案4】:

      为了根据数据库测试您的代码,如果您使用提供的 in-mem 测试它,您应该在running 调用中告诉它:

      FakeApplication(additionalConfiguration = inMemoryDatabase())
      

      以某种方式,这将迫使您的数据库围绕内部块执行启动和停止(无论是单一的还是组合的)

      编辑

      由于评论说您正在使用 mongodb,我建议您阅读此blog,其中我正在谈论我编写的一个小插件,以使 mongodb 服务器能够像嵌入式一样启动。

      我们要做的是(通过启用插件)在应用程序的同时启动和停止一个 mongodb。

      它可以帮助你...

      但是关于最初的问题,问题不应该来自正在运行的应用程序或 FakeApplication,除非 Play-Salat 或任何其他相关插件连接不良或缓存或...

      【讨论】:

      • 不幸的是我使用的是mongodb,所以我不相信我可以使用内存数据库。
      • 不久前我手动推出了自己的 Mongo 插件,并且有一个特定于测试的子类,它仅在第一个测试开始时连接到数据库,并且在测试结束时不断开连接。不理想,但它完成了工作。
      • 要通过 specs2 实现这样的目标,您只需使用 Step。一个在 Fragments 的开头启动 Mongo,另一个在结束时停止它。您应该在定义 'is' 方法的 trait 中使用它们,以便在测试中使用它们。
      • 在此页面中查找模板,这正是在解释要做什么etorreborre.github.com/specs2/guide/…
      【解决方案5】:

      这种并行测试问题在很多情况下使用运行方法时会发生。但这已经在 play2.1 中修复了。 Here 是如何解决的。如果你想在 play2.0.x 中使用这个运行,你应该像这样制作 trait:

      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】:

        我发现通过 Scala 运行单个测试类 FakeApplication 的最佳方法是遵循 下面的例子。注意'step'方法:

        @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。这对我有帮助。

          也许会有所帮助。

          扩展 BoneCPP 插件

          class NewBoneCPPlugin(val app: play.api.Application) extends BoneCPPlugin(app) {
          
          
            override def onStop() {
              //don't stop the BoneCPPlugin
              //plugin.onStop()
            }
          }
          

          在你的测试规范中应该是

              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) {
                  ...
              }
            }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-06-09
            • 1970-01-01
            • 2012-06-09
            • 1970-01-01
            • 2011-03-20
            • 2021-02-24
            • 1970-01-01
            相关资源
            最近更新 更多