Your SlideShare is downloading. ×
0
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Integration Testing With ScalaTest and MongoDB
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Integration Testing With ScalaTest and MongoDB

4,409

Published on

Our path to integration testing on Scala project using Play! framework, ScalaTest and MongoDB.

Our path to integration testing on Scala project using Play! framework, ScalaTest and MongoDB.

1 Comment
9 Likes
Statistics
Notes
No Downloads
Views
Total Views
4,409
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
48
Comments
1
Likes
9
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. INTEGRATION TESTINGWITH SCALATEST,MONGODB AND PLAY!EXPERIENCE FROM PLAY! PROJECTBy /Michal Bigos @teliatko
  • 2. AGENDA1. Integration testing, why and when2. ScalaTest for integration testing with MongoDB and Play!3. Custom DSL for integration testing and small extensions toCasbah
  • 3. CONTEXTFROM WHERE THIS ALL CAME FROM...Social network application with mobile clientsBuild on top of Play! 2Core API = REST servicesMongoDB used as main persistent storeHosted on HerokuCurrently in beta
  • 4. INTEGRATION TESTING, WHY AND WHEN?PART ONE
  • 5. DEFINITIONWikipedia:“ The phase in software testing in whichindividual software modules are combinedand tested as a group. ”
  • 6. ANOTHER ONE :)Arquillian:“ Testing business components, in particular,can be very challenging. Often, a vanilla unittest isnt sufficient for validating such acomponents behavior. Why is that? Thereason is that components in an enterpriseapplication rarely perform operations whichare strictly self-contained. Instead, theyinteract with or provide services for thegreater system. ”
  • 7. UNIT TESTS VS INTEGRATION TESTSUNIT TESTS PROPERTIES:Isolated - Checking one single concern in the system. Usuallybehavior of one class.Repeateable - It can be rerun as meny times as you want.Consistent - Every run gets the same results.Fast - Because there are loooot of them.
  • 8. UNIT TESTS VS INTEGRATION TESTSUNIT TESTS TECHNIQUES:MockingStubingxUnit frameworksFixtures in code
  • 9. UNIT TESTS VS INTEGRATION TESTSINTEGRATION TESTS PROPERTIES:Not isolated - Do not check the component or class itself, butrather integrated components together (sometimes wholeapplication).Slow - Depend on the tested component/sub-system.
  • 10. UNIT TESTS VS INTEGRATION TESTSVARIOUS INTEGRATION TESTS TYPES:Data-driven tests - Use real data and persistent store.In-container tests - Simulates real container deployment,e.g. JEE one.Performance tests - Simulate traffic growth.Acceptance tests - Simulate use cases from user point ofview.
  • 11. UNIT TESTS VS INTEGRATION TESTSKNOWN FRAMEWORKS:Data-driven tests - DBUnit, NoSQL Unit...In-container tests - Arquillian...Performance tests - JMeter...Acceptance tests - Selenium, Cucumber...
  • 12. WHY AND WHEN ?WHAT CANNOT BE WRITTEN/SIMULATED IN UNIT TESTInteraction with resources or sub-systems provided bycontainer.Interaction with external systems.Usage of declarative services applied to component atruntime.Testing whole scenarions in one test.Architectural constraints limits isolation.
  • 13. OUR CASEARCHITECTURAL CONSTRAINTS LIMITING ISOLATION:Lack of DIController depends directly on DAOobject CheckIns extends Controller {...def generate(pubId: String) = Secured.withBasic { caller: User =>Action { implicit request =>val pubOpt = PubDao.findOneById(pubId)...}}}object PubDao extends SalatDAO[Pub, ObjectId](MongoDBSetup.mongoDB("pubs")) {...}
  • 14. OUR CASEDEPENDENCIES BETWEEN COMPONENTS:
  • 15. OUR CASEGOALS:Integration tests with real DAOs and DBWriting them like unit tests
  • 16. SCALATEST FOR INTEGRATION TESTING WITHMONGODB AND PLAY!PART TWO
  • 17. TESTING STRATEGYResponsibility - encapsulate domain logicUnit test - testing the correctness of domain logic
  • 18. TESTING STRATEGYResponsibility - read/save modelIntegration test - testing the correctness of queries andmodifications, with real data and DB
  • 19. TESTING STRATEGYResponsibility - serialize/deserialize model to JSONIntegration test - testing the correctness of JSON output,using the real DAOs
  • 20. TESTING FRAMEWORKSSCALATESTStandalone xUnit frameworkCan be used within JUnit, TestNG...Pretty DSLs for writing test, especially FreeSpecPersonal preference over specs2Hooks for integration testing BeforeAndAfterandBeforeAndAfterAlltraits
  • 21. TESTING FRAMEWORKSPLAY!S TESTING SUPPORTFake applicationReal HTTP serverit should "Test something dependent on Play! application" in {running(FakeApplication()) {// Do something which depends on Play! application}}"run in a server" in {running(TestServer(3333)) {await(WS.url("http://localhost:3333").get).status must equalTo(OK)}}
  • 22. TESTING FRAMEWORKSDATA-DRIVEN TESTS FOR MONGODB- Mock implementation of the MongoDBprotocol and works purely in-memory.- More general library for testing with variousNoSQL stores. It can provide mocked or real MongoDBinstance. Relies on JUnit rules.- Platform independent way of running localMongoDB instances.jmockmongoNoSQL UnitEmbedMongo
  • 23. APPLICATION CODEConfiguration of MongoDB in application... another objecttrait MongoDBSetup {val MONGODB_URL = "mongoDB.url"val MONGODB_PORT = "mongoDB.port"val MONGODB_DB = "mongoDB.db"}object MongoDBSetup extends MongoDBSetup {private[this] val conf = current.configurationval url = conf.getString(MONGODB_URL).getOrElse(...)val port = conf.getInt(MONGODB_PORT).getOrElse(...)val db = conf.getString(MONGODB_DB).getOrElse(...)val mongoDB = MongoConnection(url, port)(db)}
  • 24. APPLICATION CODEUse of MongoDBSetupin DAOsWe have to mock or provide real DB to test the DAOobject PubDao extends SalatDAO[Pub, ObjectId](MongoDBSetup.mongoDB("pubs")) {...}
  • 25. APPLICATION CODEControllers... youve seen this alreadyobject CheckIns extends Controller {...def generate(pubId: String) = Secured.withBasic { caller: User =>Action { implicit request =>val pubOpt = PubDao.findOneById(pubId)...}}}
  • 26. OUR SOLUTIONEmbedding * to ScalaTestembedmongotrait EmbedMongoDB extends BeforeAndAfterAll { this: BeforeAndAfterAll with Suite =>def embedConnectionURL: String = { "localhost" }def embedConnectionPort: Int = { 12345 }def embedMongoDBVersion: Version = { Version.V2_2_1 }def embedDB: String = { "test" }lazy val runtime: MongodStarter = MongodStarter.getDefaultInstancelazy val mongodExe: MongodExecutable = runtime.prepare(new MongodConfig(embedMongoDBVersion, embedConnectionPort, true))lazy val mongod: MongodProcess = mongodExe.start()override def beforeAll() {mongodsuper.beforeAll()}override def afterAll() {super.afterAll()mongod.stop(); mongodExe.stop()}lazy val mongoDB = MongoConnection(embedConnectionURL, embedConnectionPort)(embedDB)}
  • 27. *we love recursion in Scala isnt it?
  • 28. OUR SOLUTIONCustom fake applicationTrait configures fake application instance for embeddedMongoDB instance. MongoDBSetupconsumes this values.trait FakeApplicationForMongoDB extends MongoDBSetup { this: EmbedMongoDB =>lazy val fakeApplicationWithMongo = FakeApplication(additionalConfiguration = Map(MONGODB_PORT -> embedConnectionPort.toString,MONGODB_URL -> embedConnectionURL,MONGODB_DB -> embedDB))}
  • 29. OUR SOLUTIONTypical test suite classclass DataDrivenMongoDBTest extends FlatSpecwith ShouldMatcherswith MustMatcherswith EmbedMongoDBwith FakeApplicationForMongoDB {...}
  • 30. OUR SOLUTIONTest method which uses mongoDBinstance directlyit should "Save and read an Object to/from MongoDB" in {// Givenval users = mongoDB("users") // this is from EmbedMongoDB trait// Whenval user = User(username = username, password = password)users += grater[User].asDBObject(user)// Thenusers.count should equal (1L)val query = MongoDBObject("username" -> username)users.findOne(query).map(grater[User].asObject(_)) must equal (Some(user))// Clean-upusers.dropCollection()}
  • 31. OUR SOLUTIONTest method which uses DAO viafakeApplicationWithMongoit should "Save and read an Object to/from MongoDB which is used in application" in {running(fakeApplicationWithMongo) {// Givenval user = User(username = username, password = password)// WhenUserDao.save(user)// ThenUserDao.findAll().find(_ == user) must equal (Some(user))}}
  • 32. OUR SOLUTIONExample of the full test from controller down to modelclass FullWSTest extends FlatSpec with ShouldMatchers with MustMatchers with EmbedMongoDB with FakeApplicationForMongoDB {val username = "test"val password = "secret"val userJson = """{"id":"%s","firstName":"","lastName":"","age":-1,"gender":-1,"state":"notFriends","photoUrl":""}""""Detail method" should "return correct Json for User" in {running(TestServer(3333, fakeApplicationWithMongo)) {val users = mongoDB("users")val user = User(username = username, password = md5(username + password))users += grater[User].asDBObject(user)val userId = user.id.toStringval response = await(WS.url("http://localhost:3333/api/user/" + userId).withAuth(username, password, AuthScheme.BASIC).get())response.status must equal (OK)response.header("Content-Type") must be (Some("application/json; charset=utf-8"))response.body must include (userJson.format(userId))}}
  • 33. }
  • 34. CUSTOM DSL FOR INTEGRATION TESTING ANDSMALL EXTENSIONS TO CASBAHPART THREEWORK IN PROGRESS
  • 35. MORE DATACreating a simple data is easy, but what about collections...We need easy way to seed them from prepared source andcheck them afterwards.
  • 36. CUSTOM DSL FOR SEEDING THE DATAPrincipleSeed the data before testUse them in test ... read, create or modifyCheck them after test (optional)
  • 37. CUSTOM DSL FOR SEEDING THE DATAInspiration - ,Based on JUnit rules or verbose codeNoSQL Unit DBUnitpublic class WhenANewBookIsCreated {@ClassRulepublic static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().mongodPath("/opt/mongo").build();@Rulepublic MongoDbRule remoteMongoDbRule = new MongoDbRule(mongoDb().databaseName("test").build());@Test@UsingDataSet(locations="initialData.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT)@ShouldMatchDataSet(location="expectedData.json")public void book_should_be_inserted_into_repository() {...}}
  • 38. This is Java. Example is taken from NoSQL Unit documentation.
  • 39. CUSTOM DSL FOR SEEDING THE DATAGoalsPure functional solutionBetter fit with ScalaTestJUnit independent
  • 40. CUSTOM DSL FOR SEEDING THE DATAResultit should "Load all Objcts from MongoDB" in {mongoDB seed ("users") fromFile ("./database/data/users.json") andseed ("pubs") fromFile ("./database/data/pubs.json")cleanUpAfter {running(fakeApplicationWithMongo) {val users = UserDao.findAll()users.size must equal (10)}}// Probably will be deprecated in next versionsmongoDB seed ("users") fromFile ("./database/data/users.json") now()running(fakeApplicationWithMongo) {val users = UserDao.findAll()users.size must equal (10)}mongoDB cleanUp ("users")}
  • 41. CUSTOM DSL FOR SEEDING THE DATAAlready implementedSeeding, clean-up and clean-up after for functional andnon-funtional usage.JSON fileformat similar to NoSQL Unit - difference, percollection basis.
  • 42. CUSTOM DSL FOR SEEDING THE DATAStill in pipelineChecking against dataset, similar to@ShouldMatchDataSet annotation of NoSQL Unit.JS file format of mongoexport. Our biggest problem hereare Dates (proprietary format).JS file format with full JavaScript functionality of mongocommand. To be able to run commands like:db.pubs.ensureIndex({loc : "2d"})NoSQL Unit JSON file format with multiple collections andseeding more collections in once.
  • 43. TOPPINGSMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAXWe dont like this*... I cannot read it, cant you?* and when possible we dont write thisdef findCheckInsBetweenDatesInPub(pubId: String,dateFrom: LocalDateTime,dateTo: LocalDateTime) : List[CheckIn] = {val query = MongoDBObject("pubId" -> new ObjectId(pubId), "created" ->MongoDBObject("$gte" -> dateFrom, "$lt" -> dateTo))collection.find(query).map(grater[CheckIn].asObject(_)).toList.headOption}
  • 44. TOPPINGSMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAXWe like pretty code a lot ... like this:Casbah query DSL is our favorite ... even when it is notperfectdef findBetweenDatesForPub(pubId: ObjectId, from: DateTime, to: DateTime) : List[CheckIn] = {find {("pubId" -> pubId) ++("created" $gte from $lt to)} sort {("created" -> -1)}}.toList.headOption
  • 45. TOPPINGSMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAXSo we enhanced it:def findBetweenDatesForPub(pubId: ObjectId, from: DateTime, to: DateTime) : List[CheckIn] = {find {("pubId" $eq pubId) ++("created" $gte from $lt to)} sort {"created" $eq -1}}.headOption
  • 46. TOPPINGSMALL ADDITIONS TO CASBAH FOR BETTER QUERYPimp my library again and again...// Adds $eq operator instead of ->implicit def queryOperatorAdditions(field: String) = new {protected val _field = field} with EqualsOptrait EqualsOp {protected def _field: Stringdef $eq[T](target: T) = MongoDBObject(_field -> target)}// Adds Scala collection headOption operation to SalatCursorimplicit def cursorAdditions[T <: AnyRef](cursor: SalatMongoCursor[T]) = new {protected val _cursor = cursor} with CursorOperations[T]trait CursorOperations[T <: AnyRef] {protected def _cursor: SalatMongoCursor[T]def headOption : Option[T] = if (_cursor.hasNext) Some(_cursor.next()) else None}
  • 47. THANKS FOR YOUR ATTENTION

×