Fail to test with specs2
Closed this issue · 21 comments
Hello and thank you for you job on ReactiveMongo
When I try to test my app with specs2, the first test is OK but the second one is always KO (whereas the test is EXACTLY the same as the first one, except for the name).
I get the message:
[info] + insert a new account
[error] ! insert a new account2
[error] TimeoutException: Futures timed out after 10000 milliseconds
Does it come from the mongodb server configuration, from my code or from ReactiveMongo?
FYI, the controller I try to test is
object Accounts extends Controller with MongoController{
val collection = db.collectionJSONCollection
def createAccount = Action(parse.json) { request =>
request.body.transform(validateAccount).map{ json =>
Async{
collection.insert(json).map{lastError =>
Created
}.recover{ case e =>
BadRequest(JsString("exception %s".format(e.getMessage)))
}
}
}.recoverTotal{ err =>
BadRequest(JsError.toFlatJson(err))
}
}
The test is
class AccountSpec extends Specification {
"Account" should {
"insert a new account" in new WithApplication {
val json = Json.obj(
"technical_name" -> "test",
"name" -> "test"
)
val Some(result) = route(FakeRequest(POST, "/accounts").
withJsonBody(json))
status(result) must equalTo(CREATED)
}
"insert a new account" in new WithApplication {
val json = Json.obj(
"technical_name" -> "test",
"name" -> "test"
)
val Some(result) = route(FakeRequest(POST, "/accounts").
withJsonBody(json))
status(result) must equalTo(CREATED)
}
}
}
After further investigations, the issue seems to come from the MongoController ... Because when I don't use it, the issue disappears
Same error if I use directly ReactiveMongoPlugin
Pump!
I got the same problem in my project. Anyone got a fix for this or the only workaround is to stop using the plugin.....
Here is a typical working Test Case:
- Use Fake Application
- Start/Stop the application manually in should{} scope
- Drop the collection manually with each in{} scope
- Wait after each insertion to DB to be finished
class HandleQuerySpec extends Specification {
"HandleQuery" should {
val additionalConfiguration: Map[String, String] = Map("mongodb.db" -> "test-db")
def application = FakeApplication(additionalConfiguration = additionalConfiguration)
step(play.api.Play.start(application))
"HandleQuery Should adhere client's query" in {
//Add some users
val usersModel: Model = Users
//Prepare data
val user1 = Json.obj(
"name" -> "Ahmed",
"age" -> 23)
val user2 = Json.obj(
"name" -> "Mohammed",
"age" -> 26)
val user3 = Json.obj(
"name" -> "Heba",
"age" -> 20)
println("DB Insertion")
val op1 = usersModel.collection.insert(user1).map(lastError =>
if (!lastError.ok)
throw new Exception("Insertion Failed"))
Await.result(op1, Duration(10000, "millis"))
val op2 = usersModel.collection.insert(user2).map(lastError =>
if (!lastError.ok)
throw new Exception("Insertion Failed"))
Await.result(op2, Duration(10000, "millis"))
val op3 = usersModel.collection.insert(user3).map(lastError =>
if (!lastError.ok)
throw new Exception("Insertion Failed"))
Await.result(op3, Duration(10000, "millis"))
//DB Query normally
}
step {
println("Dropping the DB")
Await.result(Users.collection.drop, Duration(1000, "millis")): Unit
}
step(play.api.Play.stop())
}
}
Thanks montaro.
The example you provided works perfectly and it helps reduce the test execution time.
Good work!
Do you know if this issue can be properly fixed? The single FakeApplication
instance workaround still fails with Future timeouts when your tests are split in multiple Specs files. Additional information here: https://groups.google.com/forum/#!topic/reactivemongo/yqxFtt3XOik
The short fix is to use
def collection = db.collection[JSONCollection](collectionName)
And avoid val
or lazy val
db.collection
retrieve in the current application, a reference to the current MongoPlugin. If you restart the FakeApplication many time. You will have a wrong reference.
@montaro 's approach works for only one spec. It still got 'Timeout' error when running multiple specs.
@etaty the def solution does not work for the multiple specs either.
At the moment I can only do 'test-only' in Play! to test my specs.
Hope this issues can be solved, it stops me to get my test coverage.
Just a short bump on this issue. Is anyone working on it or does anyone have any pointer on how to solve it?
Bump
I have a stackoverflow about it http://stackoverflow.com/questions/18778837/play2-reactivemongo-testing-issue-db-connection-right-after-test-fails
(not answered)
anyone from reactivemono had any update? Even if you don't plan to fix it - it would be really good to know. We have the same issue if we have multiple spec files trying to create test data in db.
It throws timeout exception. we use sequential and def db = ReactiveMongoPlugin.db instead of val
The only idea I have is to create an sh\batch script which would run spec files using test-only(instead of play test), 1 by 1, but I didn't try it yet
Did not have the time to dig it yet, but that bug is on my top priority bug list for play-reactivemongo.
Has there been any progress on this issue?
I've been experiencing the same issues as described above. For me it also seems to occur randomly when the application is running. More frequently a random amount of time/requests shortly after application start.
There is no pattern to the failing tests, other that they typically will pass if executed again.
I'm accessing the the collection using a def (not val) as suggested above. But that doesn't have any effect.
So I was curious and checked out the code. And as far as I can tell, it doesn't matter if you define the db as a val or a def in your controller. Because, eventually, it seems in the ReactiveMongoPlugin, the db is accessed as a lazy val through the ReactiveMongoHelper.
I did a little test locally, and changing that lazy val to a def seems to have fixed the problem. Not sure about the further collateral damage that might cause though.
Any ideas @sgodbillon?
I had this issue, and in my case it was definitely because of references to a MongoDB instance that was already closed. I went through all my objects and made sure they don't have any val's referencing to any resource or class that could be using the database. And I made sure that every data access class is reinitialized on application start (btw, very handy way to do that is to create your own plugin). Now I can run multiple specs without any issues.
any progress on this issue?
Well...changing the ReactiveMongoHelper.db from lazy val to def seems to have taken care of the issues I had when testing. But I'm still seeing sporadic timeouts on queries when running the application. Considering to pick up on the suggestion from @tjjalava.
Here is a working test, for multiple test classes.
You can read the documentation in https://www.playframework.com/documentation/2.2.x/ScalaFunctionalTestingWithScalaTest
http://doc.scalatest.org/plus-play/1.0.0/index.html#org.scalatestplus.play.ConfiguredApp
import play.api.test._
import org.scalatest._
import org.scalatestplus.play._
import play.api.{Play, Application}
//master test class that triggers the other test class, the FakeApplication is initialized in this class before //all other test classes
class MasterSpec extends Suites(
new TestSpec1,
new TestSpec2
) with OneAppPerSuite {
//your configuration for the FakeApplication
}
class TestSpec1 extends PlaySpec with ConfiguredApp {
//your test code
}
class TestSpec2 extends PlaySpec with ConfiguredApp {
// your test code
}
Hi, I suspect that issue if more related to the lifecycle of Play plugin (which will be changed in the next Play release and already have been updated for some 2.3.x minor versions). I guess for now that would be better to run test with a connection manager by tests themselves (rather than a fake Play app).