SlideShare une entreprise Scribd logo
1  sur  48
Télécharger pour lire hors ligne
INTEGRATION TESTING
WITH SCALATEST,
MONGODB AND PLAY!
EXPERIENCE FROM PLAY! PROJECT
By /Michal Bigos @teliatko
AGENDA
1. Integration testing, why and when
2. ScalaTest for integration testing with MongoDB and Play!
3. Custom DSL for integration testing and small extensions to
Casbah
CONTEXT
FROM WHERE THIS ALL CAME FROM...
Social network application with mobile clients
Build on top of Play! 2
Core API = REST services
MongoDB used as main persistent store
Hosted on Heroku
Currently in beta
INTEGRATION TESTING, WHY AND WHEN?
PART ONE
DEFINITION
Wikipedia:
“ The phase in software testing in which
individual software modules are combined
and tested as a group. ”
ANOTHER ONE :)
Arquillian:
“ Testing business components, in particular,
can be very challenging. Often, a vanilla unit
test isn't sufficient for validating such a
component's behavior. Why is that? The
reason is that components in an enterprise
application rarely perform operations which
are strictly self-contained. Instead, they
interact with or provide services for the
greater system. ”
UNIT TESTS 'VS' INTEGRATION TESTS
UNIT TESTS PROPERTIES:
Isolated - Checking one single concern in the system. Usually
behavior 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.
UNIT TESTS 'VS' INTEGRATION TESTS
UNIT TESTS TECHNIQUES:
Mocking
Stubing
xUnit frameworks
Fixtures in code
UNIT TESTS 'VS' INTEGRATION TESTS
INTEGRATION TESTS PROPERTIES:
Not isolated - Do not check the component or class itself, but
rather integrated components together (sometimes whole
application).
Slow - Depend on the tested component/sub-system.
UNIT TESTS 'VS' INTEGRATION TESTS
VARIOUS 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 of
view.
UNIT TESTS 'VS' INTEGRATION TESTS
KNOWN FRAMEWORKS:
Data-driven tests - DBUnit, NoSQL Unit...
In-container tests - Arquillian...
Performance tests - JMeter...
Acceptance tests - Selenium, Cucumber...
WHY AND WHEN ?
WHAT CANNOT BE WRITTEN/SIMULATED IN UNIT TEST
Interaction with resources or sub-systems provided by
container.
Interaction with external systems.
Usage of declarative services applied to component at
runtime.
Testing whole scenarions in one test.
Architectural constraints limits isolation.
OUR CASE
ARCHITECTURAL CONSTRAINTS LIMITING ISOLATION:
Lack of DI
Controller depends directly on DAO
object 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
")) {
...
}
OUR CASE
DEPENDENCIES BETWEEN COMPONENTS:
OUR CASE
GOALS:
Integration tests with real DAOs and DB
Writing them like unit tests
SCALATEST FOR INTEGRATION TESTING WITH
MONGODB AND PLAY!
PART TWO
TESTING STRATEGY
Responsibility - encapsulate domain logic
Unit test - testing the correctness of domain logic
TESTING STRATEGY
Responsibility - read/save model
Integration test - testing the correctness of queries and
modifications, with real data and DB
TESTING STRATEGY
Responsibility - serialize/deserialize model to JSON
Integration test - testing the correctness of JSON output,
using the real DAOs
TESTING FRAMEWORKS
SCALATEST
Standalone xUnit framework
Can be used within JUnit, TestNG...
Pretty DSLs for writing test, especially FreeSpec
Personal preference over specs2
Hooks for integration testing BeforeAndAfterand
BeforeAndAfterAlltraits
TESTING FRAMEWORKS
PLAY!'S TESTING SUPPORT
Fake application
Real HTTP server
it 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)
}
}
TESTING FRAMEWORKS
DATA-DRIVEN TESTS FOR MONGODB
- Mock implementation of the MongoDB
protocol and works purely in-memory.
- More general library for testing with various
NoSQL stores. It can provide mocked or real MongoDB
instance. Relies on JUnit rules.
- Platform independent way of running local
MongoDB instances.
jmockmongo
NoSQL Unit
EmbedMongo
APPLICATION CODE
Configuration of MongoDB in application
... another object
trait 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.configuration
val 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)
}
APPLICATION CODE
Use of MongoDBSetupin DAOs
We have to mock or provide real DB to test the DAO
object PubDao extends SalatDAO[Pub, ObjectId](MongoDBSetup.mongoDB("pubs
")) {
...
}
APPLICATION CODE
Controllers
... you've seen this already
object CheckIns extends Controller {
...
def generate(pubId: String) = Secured.withBasic { caller: User =>
Action { implicit request =>
val pubOpt = PubDao.findOneById(pubId)
...
}
}
}
OUR SOLUTION
Embedding * to ScalaTestembedmongo
trait 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.getDefaultInstance
lazy val mongodExe: MongodExecutable = runtime.prepare(new MongodConfig(embedMongoDBV
ersion, embedConnectionPort, true))
lazy val mongod: MongodProcess = mongodExe.start()
override def beforeAll() {
mongod
super.beforeAll()
}
override def afterAll() {
super.afterAll()
mongod.stop(); mongodExe.stop()
}
lazy val mongoDB = MongoConnection(embedConnectionURL, embedConnectionPort)(embedDB)
}
*we love recursion in Scala isn't it?
OUR SOLUTION
Custom fake application
Trait configures fake application instance for embedded
MongoDB 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
))
}
OUR SOLUTION
Typical test suite class
class DataDrivenMongoDBTest extends FlatSpec
with ShouldMatchers
with MustMatchers
with EmbedMongoDB
with FakeApplicationForMongoDB {
...
}
OUR SOLUTION
Test method which uses mongoDBinstance directly
it should "Save and read an Object to/from MongoDB" in {
// Given
val users = mongoDB("users") // this is from EmbedMongoDB trait
// When
val user = User(username = username, password = password)
users += grater[User].asDBObject(user)
// Then
users.count should equal (1L)
val query = MongoDBObject("username" -> username)
users.findOne(query).map(grater[User].asObject(_)) must equal (Some(user))
// Clean-up
users.dropCollection()
}
OUR SOLUTION
Test method which uses DAO via
fakeApplicationWithMongo
it should "Save and read an Object to/from MongoDB which is used in application" in {
running(fakeApplicationWithMongo) {
// Given
val user = User(username = username, password = password)
// When
UserDao.save(user)
// Then
UserDao.findAll().find(_ == user) must equal (Some(user))
}
}
OUR SOLUTION
Example of the full test from controller down to model
class FullWSTest extends FlatSpec with ShouldMatchers with MustMatchers with EmbedMongo
DB 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.toString
val 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))
}
}
}
CUSTOM DSL FOR INTEGRATION TESTING AND
SMALL EXTENSIONS TO CASBAH
PART THREE
WORK IN PROGRESS
MORE DATA
Creating a simple data is easy, but what about collections...
We need easy way to seed them from prepared source and
check them afterwards.
CUSTOM DSL FOR SEEDING THE DATA
Principle
Seed the data before test
Use them in test ... read, create or modify
Check them after test (optional)
CUSTOM DSL FOR SEEDING THE DATA
Inspiration - ,
Based on JUnit rules or verbose code
NoSQL Unit DBUnit
public class WhenANewBookIsCreated {
@ClassRule
public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().mongodPath("/
opt/mongo").build();
@Rule
public MongoDbRule remoteMongoDbRule = new MongoDbRule(mongoDb().databaseName("test
").build());
@Test
@UsingDataSet(locations="initialData.json", loadStrategy=LoadStrategyEnum.CLEAN_INS
ERT)
@ShouldMatchDataSet(location="expectedData.json")
public void book_should_be_inserted_into_repository() {
...
}
}
This is Java. Example is taken from NoSQL Unit documentation.
CUSTOM DSL FOR SEEDING THE DATA
Goals
Pure functional solution
Better fit with ScalaTest
JUnit independent
CUSTOM DSL FOR SEEDING THE DATA
Result
it should "Load all Objcts from MongoDB" in {
mongoDB seed ("users") fromFile ("./database/data/users.json") and
seed ("pubs") fromFile ("./database/data/pubs.json")
cleanUpAfter {
running(fakeApplicationWithMongo) {
val users = UserDao.findAll()
users.size must equal (10)
}
}
// Probably will be deprecated in next versions
mongoDB seed ("users") fromFile ("./database/data/users.json") now()
running(fakeApplicationWithMongo) {
val users = UserDao.findAll()
users.size must equal (10)
}
mongoDB cleanUp ("users")
}
CUSTOM DSL FOR SEEDING THE DATA
Already implemented
Seeding, clean-up and clean-up after for functional and
non-funtional usage.
JSON fileformat similar to NoSQL Unit - difference, per
collection basis.
CUSTOM DSL FOR SEEDING THE DATA
Still in pipeline
Checking against dataset, similar to
@ShouldMatchDataSet annotation of NoSQL Unit.
JS file format of mongoexport. Our biggest problem here
are Dates (proprietary format).
JS file format with full JavaScript functionality of mongo
command. To be able to run commands like:
db.pubs.ensureIndex({loc : "2d"})
NoSQL Unit JSON file format with multiple collections and
seeding more collections in once.
TOPPING
SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX
We don't like this*
... I cannot read it, can't you?
* and when possible we don't write this
def 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.headOpt
ion
}
TOPPING
SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX
We like pretty code a lot ... like this:
Casbah query DSL is our favorite ... even when it is not
perfect
def findBetweenDatesForPub(pubId: ObjectId, from: DateTime, to: DateTime
) : List[CheckIn] = {
find {
("pubId" -> pubId) ++
("created" $gte from $lt to)
} sort {
("created" -> -1)
}
}.toList.headOption
TOPPING
SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX
So 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
TOPPING
SMALL ADDITIONS TO CASBAH FOR BETTER QUERY
Pimp my library again and again...
// Adds $eq operator instead of ->
implicit def queryOperatorAdditions(field: String) = new {
protected val _field = field
} with EqualsOp
trait EqualsOp {
protected def _field: String
def $eq[T](target: T) = MongoDBObject(_field -> target)
}
// Adds Scala collection headOption operation to SalatCursor
implicit 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
}
THANKS FOR YOUR ATTENTION

Contenu connexe

Tendances

Tendances (20)

Jquery- One slide completing all JQuery
Jquery- One slide completing all JQueryJquery- One slide completing all JQuery
Jquery- One slide completing all JQuery
 
AngularJS Unit Test
AngularJS Unit TestAngularJS Unit Test
AngularJS Unit Test
 
Codeception
CodeceptionCodeception
Codeception
 
PHP Unit Testing in Yii
PHP Unit Testing in YiiPHP Unit Testing in Yii
PHP Unit Testing in Yii
 
Efficient JavaScript Unit Testing, May 2012
Efficient JavaScript Unit Testing, May 2012Efficient JavaScript Unit Testing, May 2012
Efficient JavaScript Unit Testing, May 2012
 
Automated php unit testing in drupal 8
Automated php unit testing in drupal 8Automated php unit testing in drupal 8
Automated php unit testing in drupal 8
 
Tellurium.A.New.Approach.For.Web.Testing
Tellurium.A.New.Approach.For.Web.TestingTellurium.A.New.Approach.For.Web.Testing
Tellurium.A.New.Approach.For.Web.Testing
 
Test all the things! Automated testing with Drupal 8
Test all the things! Automated testing with Drupal 8Test all the things! Automated testing with Drupal 8
Test all the things! Automated testing with Drupal 8
 
Codeception presentation
Codeception presentationCodeception presentation
Codeception presentation
 
Unit-testing and E2E testing in JS
Unit-testing and E2E testing in JSUnit-testing and E2E testing in JS
Unit-testing and E2E testing in JS
 
Selenium Overview
Selenium OverviewSelenium Overview
Selenium Overview
 
081107 Sammy Eclipse Summit2
081107   Sammy   Eclipse Summit2081107   Sammy   Eclipse Summit2
081107 Sammy Eclipse Summit2
 
Ten Minutes To Tellurium
Ten Minutes To TelluriumTen Minutes To Tellurium
Ten Minutes To Tellurium
 
Protractor Testing Automation Tool Framework / Jasmine Reporters
Protractor Testing Automation Tool Framework / Jasmine ReportersProtractor Testing Automation Tool Framework / Jasmine Reporters
Protractor Testing Automation Tool Framework / Jasmine Reporters
 
Automated Smoke Tests with Protractor
Automated Smoke Tests with ProtractorAutomated Smoke Tests with Protractor
Automated Smoke Tests with Protractor
 
Automation testing with Drupal 8
Automation testing with Drupal 8Automation testing with Drupal 8
Automation testing with Drupal 8
 
ATAGTR2017 Upgrading a mobile tester's weapons with advanced debugging
ATAGTR2017 Upgrading a mobile tester's weapons with advanced debuggingATAGTR2017 Upgrading a mobile tester's weapons with advanced debugging
ATAGTR2017 Upgrading a mobile tester's weapons with advanced debugging
 
Dart for Java Developers
Dart for Java DevelopersDart for Java Developers
Dart for Java Developers
 
Advanced Selenium Workshop
Advanced Selenium WorkshopAdvanced Selenium Workshop
Advanced Selenium Workshop
 
Selenium interview questions and answers
Selenium interview questions and answersSelenium interview questions and answers
Selenium interview questions and answers
 

Similaire à Integration Testing With ScalaTest and MongoDB

Javascript unit testing, yes we can e big
Javascript unit testing, yes we can   e bigJavascript unit testing, yes we can   e big
Javascript unit testing, yes we can e big
Andy Peterson
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development
Mahmoud Hamed Mahmoud
 

Similaire à Integration Testing With ScalaTest and MongoDB (20)

Test Automation for NoSQL Databases
Test Automation for NoSQL DatabasesTest Automation for NoSQL Databases
Test Automation for NoSQL Databases
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
 
Will your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry GervinWill your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry Gervin
 
Knockoutjs databinding
Knockoutjs databindingKnockoutjs databinding
Knockoutjs databinding
 
Bringing the light to the client with KnockoutJS
Bringing the light to the client with KnockoutJSBringing the light to the client with KnockoutJS
Bringing the light to the client with KnockoutJS
 
MVC pattern for widgets
MVC pattern for widgetsMVC pattern for widgets
MVC pattern for widgets
 
Javascript unit testing, yes we can e big
Javascript unit testing, yes we can   e bigJavascript unit testing, yes we can   e big
Javascript unit testing, yes we can e big
 
Testing the frontend
Testing the frontendTesting the frontend
Testing the frontend
 
Take Control of your Integration Testing with TestContainers
Take Control of your Integration Testing with TestContainersTake Control of your Integration Testing with TestContainers
Take Control of your Integration Testing with TestContainers
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development
 
EJB et WS (Montreal JUG - 12 mai 2011)
EJB et WS (Montreal JUG - 12 mai 2011)EJB et WS (Montreal JUG - 12 mai 2011)
EJB et WS (Montreal JUG - 12 mai 2011)
 
Android Unit Test
Android Unit TestAndroid Unit Test
Android Unit Test
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
 
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web TestingBDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
 
React Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsReact Native for multi-platform mobile applications
React Native for multi-platform mobile applications
 
Data access
Data accessData access
Data access
 
Dependency injection in scala
Dependency injection in scalaDependency injection in scala
Dependency injection in scala
 
Javascript first-class citizenery
Javascript first-class citizeneryJavascript first-class citizenery
Javascript first-class citizenery
 
Testing your application on Google App Engine
Testing your application on Google App EngineTesting your application on Google App Engine
Testing your application on Google App Engine
 
Testing Your Application On Google App Engine
Testing Your Application On Google App EngineTesting Your Application On Google App Engine
Testing Your Application On Google App Engine
 

Dernier

KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
Cara Menggugurkan Kandungan 087776558899
 
February 2024 Recommendations for newsletter
February 2024 Recommendations for newsletterFebruary 2024 Recommendations for newsletter
February 2024 Recommendations for newsletter
ssuserdfec6a
 
the Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentationthe Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentation
brynpueblos04
 

Dernier (10)

SIKP311 Sikolohiyang Pilipino - Ginhawa.pptx
SIKP311 Sikolohiyang Pilipino - Ginhawa.pptxSIKP311 Sikolohiyang Pilipino - Ginhawa.pptx
SIKP311 Sikolohiyang Pilipino - Ginhawa.pptx
 
Pokemon Go... Unraveling the Conspiracy Theory
Pokemon Go... Unraveling the Conspiracy TheoryPokemon Go... Unraveling the Conspiracy Theory
Pokemon Go... Unraveling the Conspiracy Theory
 
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
KLINIK BATA Jual obat penggugur kandungan 087776558899 ABORSI JANIN KEHAMILAN...
 
WOMEN EMPOWERMENT women empowerment.pptx
WOMEN EMPOWERMENT women empowerment.pptxWOMEN EMPOWERMENT women empowerment.pptx
WOMEN EMPOWERMENT women empowerment.pptx
 
2023 - Between Philosophy and Practice: Introducing Yoga
2023 - Between Philosophy and Practice: Introducing Yoga2023 - Between Philosophy and Practice: Introducing Yoga
2023 - Between Philosophy and Practice: Introducing Yoga
 
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
Call Girls In Mumbai Just Genuine Call ☎ 7738596112✅ Call Girl Andheri East G...
 
February 2024 Recommendations for newsletter
February 2024 Recommendations for newsletterFebruary 2024 Recommendations for newsletter
February 2024 Recommendations for newsletter
 
the Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentationthe Husband rolesBrown Aesthetic Cute Group Project Presentation
the Husband rolesBrown Aesthetic Cute Group Project Presentation
 
Dadar West Escorts 🥰 8617370543 Call Girls Offer VIP Hot Girls
Dadar West Escorts 🥰 8617370543 Call Girls Offer VIP Hot GirlsDadar West Escorts 🥰 8617370543 Call Girls Offer VIP Hot Girls
Dadar West Escorts 🥰 8617370543 Call Girls Offer VIP Hot Girls
 
March 2023 Recommendations for newsletter
March 2023 Recommendations for newsletterMarch 2023 Recommendations for newsletter
March 2023 Recommendations for newsletter
 

Integration Testing With ScalaTest and MongoDB

  • 1. INTEGRATION TESTING WITH SCALATEST, MONGODB AND PLAY! EXPERIENCE FROM PLAY! PROJECT By /Michal Bigos @teliatko
  • 2. AGENDA 1. Integration testing, why and when 2. ScalaTest for integration testing with MongoDB and Play! 3. Custom DSL for integration testing and small extensions to Casbah
  • 3. CONTEXT FROM WHERE THIS ALL CAME FROM... Social network application with mobile clients Build on top of Play! 2 Core API = REST services MongoDB used as main persistent store Hosted on Heroku Currently in beta
  • 4. INTEGRATION TESTING, WHY AND WHEN? PART ONE
  • 5. DEFINITION Wikipedia: “ The phase in software testing in which individual software modules are combined and tested as a group. ”
  • 6. ANOTHER ONE :) Arquillian: “ Testing business components, in particular, can be very challenging. Often, a vanilla unit test isn't sufficient for validating such a component's behavior. Why is that? The reason is that components in an enterprise application rarely perform operations which are strictly self-contained. Instead, they interact with or provide services for the greater system. ”
  • 7. UNIT TESTS 'VS' INTEGRATION TESTS UNIT TESTS PROPERTIES: Isolated - Checking one single concern in the system. Usually behavior 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 TESTS UNIT TESTS TECHNIQUES: Mocking Stubing xUnit frameworks Fixtures in code
  • 9. UNIT TESTS 'VS' INTEGRATION TESTS INTEGRATION TESTS PROPERTIES: Not isolated - Do not check the component or class itself, but rather integrated components together (sometimes whole application). Slow - Depend on the tested component/sub-system.
  • 10. UNIT TESTS 'VS' INTEGRATION TESTS VARIOUS 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 of view.
  • 11. UNIT TESTS 'VS' INTEGRATION TESTS KNOWN 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 TEST Interaction with resources or sub-systems provided by container. Interaction with external systems. Usage of declarative services applied to component at runtime. Testing whole scenarions in one test. Architectural constraints limits isolation.
  • 13. OUR CASE ARCHITECTURAL CONSTRAINTS LIMITING ISOLATION: Lack of DI Controller depends directly on DAO object 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.
  • 16. OUR CASE GOALS: Integration tests with real DAOs and DB Writing them like unit tests
  • 17. SCALATEST FOR INTEGRATION TESTING WITH MONGODB AND PLAY! PART TWO
  • 18. TESTING STRATEGY Responsibility - encapsulate domain logic Unit test - testing the correctness of domain logic
  • 19. TESTING STRATEGY Responsibility - read/save model Integration test - testing the correctness of queries and modifications, with real data and DB
  • 20. TESTING STRATEGY Responsibility - serialize/deserialize model to JSON Integration test - testing the correctness of JSON output, using the real DAOs
  • 21. TESTING FRAMEWORKS SCALATEST Standalone xUnit framework Can be used within JUnit, TestNG... Pretty DSLs for writing test, especially FreeSpec Personal preference over specs2 Hooks for integration testing BeforeAndAfterand BeforeAndAfterAlltraits
  • 22. TESTING FRAMEWORKS PLAY!'S TESTING SUPPORT Fake application Real HTTP server it 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) } }
  • 23. TESTING FRAMEWORKS DATA-DRIVEN TESTS FOR MONGODB - Mock implementation of the MongoDB protocol and works purely in-memory. - More general library for testing with various NoSQL stores. It can provide mocked or real MongoDB instance. Relies on JUnit rules. - Platform independent way of running local MongoDB instances. jmockmongo NoSQL Unit EmbedMongo
  • 24. APPLICATION CODE Configuration of MongoDB in application ... another object trait 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.configuration val 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) }
  • 25. APPLICATION CODE Use of MongoDBSetupin DAOs We have to mock or provide real DB to test the DAO object PubDao extends SalatDAO[Pub, ObjectId](MongoDBSetup.mongoDB("pubs ")) { ... }
  • 26. APPLICATION CODE Controllers ... you've seen this already object CheckIns extends Controller { ... def generate(pubId: String) = Secured.withBasic { caller: User => Action { implicit request => val pubOpt = PubDao.findOneById(pubId) ... } } }
  • 27. OUR SOLUTION Embedding * to ScalaTestembedmongo trait 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.getDefaultInstance lazy val mongodExe: MongodExecutable = runtime.prepare(new MongodConfig(embedMongoDBV ersion, embedConnectionPort, true)) lazy val mongod: MongodProcess = mongodExe.start() override def beforeAll() { mongod super.beforeAll() } override def afterAll() { super.afterAll() mongod.stop(); mongodExe.stop() } lazy val mongoDB = MongoConnection(embedConnectionURL, embedConnectionPort)(embedDB) }
  • 28. *we love recursion in Scala isn't it?
  • 29. OUR SOLUTION Custom fake application Trait configures fake application instance for embedded MongoDB 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 )) }
  • 30. OUR SOLUTION Typical test suite class class DataDrivenMongoDBTest extends FlatSpec with ShouldMatchers with MustMatchers with EmbedMongoDB with FakeApplicationForMongoDB { ... }
  • 31. OUR SOLUTION Test method which uses mongoDBinstance directly it should "Save and read an Object to/from MongoDB" in { // Given val users = mongoDB("users") // this is from EmbedMongoDB trait // When val user = User(username = username, password = password) users += grater[User].asDBObject(user) // Then users.count should equal (1L) val query = MongoDBObject("username" -> username) users.findOne(query).map(grater[User].asObject(_)) must equal (Some(user)) // Clean-up users.dropCollection() }
  • 32. OUR SOLUTION Test method which uses DAO via fakeApplicationWithMongo it should "Save and read an Object to/from MongoDB which is used in application" in { running(fakeApplicationWithMongo) { // Given val user = User(username = username, password = password) // When UserDao.save(user) // Then UserDao.findAll().find(_ == user) must equal (Some(user)) } }
  • 33. OUR SOLUTION Example of the full test from controller down to model class FullWSTest extends FlatSpec with ShouldMatchers with MustMatchers with EmbedMongo DB 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.toString val 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)) } }
  • 34. }
  • 35. CUSTOM DSL FOR INTEGRATION TESTING AND SMALL EXTENSIONS TO CASBAH PART THREE WORK IN PROGRESS
  • 36. MORE DATA Creating a simple data is easy, but what about collections... We need easy way to seed them from prepared source and check them afterwards.
  • 37. CUSTOM DSL FOR SEEDING THE DATA Principle Seed the data before test Use them in test ... read, create or modify Check them after test (optional)
  • 38. CUSTOM DSL FOR SEEDING THE DATA Inspiration - , Based on JUnit rules or verbose code NoSQL Unit DBUnit public class WhenANewBookIsCreated { @ClassRule public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().mongodPath("/ opt/mongo").build(); @Rule public MongoDbRule remoteMongoDbRule = new MongoDbRule(mongoDb().databaseName("test ").build()); @Test @UsingDataSet(locations="initialData.json", loadStrategy=LoadStrategyEnum.CLEAN_INS ERT) @ShouldMatchDataSet(location="expectedData.json") public void book_should_be_inserted_into_repository() { ... } }
  • 39. This is Java. Example is taken from NoSQL Unit documentation.
  • 40. CUSTOM DSL FOR SEEDING THE DATA Goals Pure functional solution Better fit with ScalaTest JUnit independent
  • 41. CUSTOM DSL FOR SEEDING THE DATA Result it should "Load all Objcts from MongoDB" in { mongoDB seed ("users") fromFile ("./database/data/users.json") and seed ("pubs") fromFile ("./database/data/pubs.json") cleanUpAfter { running(fakeApplicationWithMongo) { val users = UserDao.findAll() users.size must equal (10) } } // Probably will be deprecated in next versions mongoDB seed ("users") fromFile ("./database/data/users.json") now() running(fakeApplicationWithMongo) { val users = UserDao.findAll() users.size must equal (10) } mongoDB cleanUp ("users") }
  • 42. CUSTOM DSL FOR SEEDING THE DATA Already implemented Seeding, clean-up and clean-up after for functional and non-funtional usage. JSON fileformat similar to NoSQL Unit - difference, per collection basis.
  • 43. CUSTOM DSL FOR SEEDING THE DATA Still in pipeline Checking against dataset, similar to @ShouldMatchDataSet annotation of NoSQL Unit. JS file format of mongoexport. Our biggest problem here are Dates (proprietary format). JS file format with full JavaScript functionality of mongo command. To be able to run commands like: db.pubs.ensureIndex({loc : "2d"}) NoSQL Unit JSON file format with multiple collections and seeding more collections in once.
  • 44. TOPPING SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX We don't like this* ... I cannot read it, can't you? * and when possible we don't write this def 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.headOpt ion }
  • 45. TOPPING SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX We like pretty code a lot ... like this: Casbah query DSL is our favorite ... even when it is not perfect def findBetweenDatesForPub(pubId: ObjectId, from: DateTime, to: DateTime ) : List[CheckIn] = { find { ("pubId" -> pubId) ++ ("created" $gte from $lt to) } sort { ("created" -> -1) } }.toList.headOption
  • 46. TOPPING SMALL ADDITIONS TO CASBAH FOR BETTER QUERY SYNTAX So 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
  • 47. TOPPING SMALL ADDITIONS TO CASBAH FOR BETTER QUERY Pimp my library again and again... // Adds $eq operator instead of -> implicit def queryOperatorAdditions(field: String) = new { protected val _field = field } with EqualsOp trait EqualsOp { protected def _field: String def $eq[T](target: T) = MongoDBObject(_field -> target) } // Adds Scala collection headOption operation to SalatCursor implicit 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 }
  • 48. THANKS FOR YOUR ATTENTION