2013-06-24 5 views
5

Voglio eseguire test unitari per un'app Play 2 Scala utilizzando la stessa impostazione del database utilizzata nella produzione: Liscio con Postgres. Quanto segue non riesce con "java.sql.SQLException: tentativo di ottenere una connessione da un pool che è già stato arrestato." al 2 ° test.Esegui 2.1 Test unitario con Slick e Postgres

package controllers 

import org.specs2.mutable._ 
import play.api.db.DB 
import play.api.Play.current 
import play.api.test._ 
import play.api.test.Helpers._ 
import scala.slick.driver.PostgresDriver.simple._ 

class BogusTest extends Specification { 

    def postgresDatabase(name: String = "default", 
         options: Map[String, String] = Map.empty): Map[String, String] = 
    Map(
     "db.test.driver" -> "org.postgresql.Driver", 
     "db.test.user"  -> "postgres", 
     "db.test.password" -> "blah", 
     "db.test.url"  -> "jdbc:postgresql://localhost/blah" 
    ) 

    def fakeApp[T](block: => T): T = 
    running(FakeApplication(additionalConfiguration = 
     postgresDatabase("test") ++ Map("evolutionplugin" -> "disabled"))) { 
     def database = Database.forDataSource(DB.getDataSource("test")) 
     database.withSession { implicit s: Session => block } 
     } 

    "Fire 1" should { 
    "do something" in fakeApp { 
     success 
    } 
    } 

    "Fire 2" should { 
    "do something else" in fakeApp { 
     success 
    } 
    } 
} 

ho eseguito il test come questo:

$ play -Dconfig.file=`pwd`/conf/dev.conf "test-only controllers.BogusTest" 

Due altri misteri:

1) Tutti i test eseguiti, anche se chiedo solo BogusTest per eseguire

2) application.conf viene sempre utilizzato, non def.conf e le informazioni sul driver provengono da application.conf, non dalle informazioni configurate nel codice.

+0

Vedere http://stackoverflow.com/questions/15399161/how-do-i-specify-a-config-file-with-sbt-0-12-2-for-sbt-test per informazioni su come specificare un diverso file di configurazione con 'test'. –

+0

Grazie, sembra che questo dovrebbe occuparsi del mistero n. 2. Riesci a far luce sul problema principale? –

+0

Anche io sto incontrando lo stesso problema. Anche senza utilizzare il tratto Around di specs2, e semplicemente il wrapping all'interno di 'in {running (fakeApp) {Database.forDataSource (DB.getDataSource()) withSession {}}}' fornisce anche questa eccezione. – Meredith

risposta

4

Questa è una risposta provvisoria come ho attualmente testato su Play 2.2.0 e non riesco a riprodurre il bug, utilizzando un database MySQL.

Penso che ci possa essere un bug molto insidioso nel codice. Prima di tutto, se si esplora l'implementazione DBPlugin fornito da Play, BoneCPPPlugin:

/** 
    * Closes all data sources. 
    */ 
    override def onStop() { 
    dbApi.datasources.foreach { 
     case (ds, _) => try { 
     dbApi.shutdownPool(ds) 
     } catch { case NonFatal(_) => } 
    } 
    val drivers = DriverManager.getDrivers() 
    while (drivers.hasMoreElements) { 
     val driver = drivers.nextElement 
     DriverManager.deregisterDriver(driver) 
    } 
    } 

si vede che il metodo onStop() chiude il pool di connessioni. Quindi è chiaro, stai fornendo al secondo esempio di prova un'applicazione che è già stata interrotta (e quindi i suoi plugin sono fermati e il pool di connessioni db chiuso).

Scalatests e specs2 eseguire il test in parallelo, e si può contare su l'assistente di prova, perché è thread-safe:

def running[T](fakeApp: FakeApplication)(block: => T): T = { 
     synchronized { 
      try { 
      Play.start(fakeApp) 
      block 
      } finally { 
      Play.stop() 
      play.api.libs.ws.WS.resetClient() 
      } 
     } 
     } 

Tuttavia, quando si esegue

DB.getDataSource("test") 

dal codice sorgente di gioco:

def getDataSource(name: String = "default")(implicit app: Application): DataSource = app.plugin[DBPlugin].map(_.api.getDataSource(name)).getOrElse(error) 

Quindi qui c'è un implicito, non viene risolto a FakeApplication (è n ot implicito in ambito !!!), ma a Play.current e sembra che nel secondo caso, questo non è quello che ti aspettavi, Play.current punta ancora all'istanza precedente di FakeApplication: probabilmente dipende da come implicita vengono acquisite in chiusure

Se invece, refactoring il metodo fakeapp, è possibile garantire l'applicazione appena creata viene utilizzato per risolvere l'implicito (si può sempre rendere esplicito il valore di un parametro implicito)

def fakeApp[T](block: => T): T = { 
    val fakeApplication = FakeApplication(additionalConfiguration = 
     postgresDatabase("test") ++ Map("evolutionplugin" -> "disabled")) 
     running(fakeApplication) { 
     def database = Database.forDataSource(DB.getDataSource("test")(fakeApplication)) 
     database.withSession { implicit s: Session => block } 
     } 
    } 
+0

Edmondo, eccellente investigazione! Questo fa andare avanti le cose. Ora ottengo "RuntimeException: There is no started application" quando si esegue dalla riga di comando, sfortunatamente. Ho fatto un riassunto con tutti i file rilevanti https://gist.github.com/mslinn/8046542 –

+0

puoi fornire un progetto sbt su un repository github? Mi manca application.conf che viene importato in test.conf e sarebbe troppo complicato per il debug. Nel tuo senso vedo che hai ancora play.api.Play.current come importazione. Vorrei rimuoverlo per vedere cosa succede – Edmondo1984

+0

Riprovare con la modifica che ho apportato qui - https://gist.github.com/drstevens/8048116/f181c671c64df210e37197a30a5801e25f6014fc – drstevens