user2066049
user2066049

Reputation: 1371

best way to test Slick session/transaction code

for creating datasource I have

object MyDataSource {
 priavte lazy val dataSource: javax.sql.DataSource = {
 val ds = new BasicDataSource
 val conf = ConfigFactory.load()

 val url = conf.getString("jdbc-url")
 val driver = conf.getString("jdbc-driver")
 val username = conf.getString("db-username")
 val password = conf.getString("db-password")
 val port = conf.getString("db-port")

 val maxActive = conf.getInt("max-active")

 val maxIdle = conf.getInt("max-idle")
 val initSize = conf.getInt("init-size")

 ds.setDriverClassName(driver)
 ds.setUsername(username)
 ds.setPassword(password)
 ds.setMaxActive(maxActive)
 ds.setMaxIdle(maxIdle)
 ds.setInitialSize(initSize)

 ds.setUrl(url)
 ds
 } 

lazy val database = Database.forDataSource(dataSource)
}

MyDataSource is used as below

def insertCompany = {    
   MyDataSource.database.withSession{ implicit session =>
   company.insert(companyRow)

   }
}

Now for testing I have trait DatabaseSpec which loads test database(pointing to test db) config and has following fixture

def withSession(testCode: Session => Any) {
val session = postgres.createSession()
session.conn.setAutoCommit(false)
try {
  testCode(session)
} finally {
  session.rollback()
  session.close()
}

}

And test code can then mix in DatabaseSpec and use withSession to test transactional code.

Now question is what's the best practice in keeping MyDataSource.database.withSession abstracted away from DataSource in insertCompany so that method can be tested with DatabaseSpec and pointing to test db?

Upvotes: 1

Views: 1061

Answers (1)

cvogt
cvogt

Reputation: 11270

The best way to be able to exchange a value, .e.g for prod and testing is by parameterizing your code in that value. E.g.

def insertCompany(db: Database) = db.withSession(company.insert(companyRow)(_))

or

class DAO(db:Database){
    def insertCompany = db.withSession(company.insert(companyRow)(_))
}

Keep it simple. Avoid unnecessary complexity like the Cake pattern, DI frameworks or mixin composition for this.

If you need to pass multiple values around... aggregate them into a "config"-class. Compose multiple config classes with different purposes to target different things, if you want to avoid writing one huge config class as stuff accumulates.

If you find yourself passing config objects to all your functions, you can mark them as implicit, that saves you at least the call-site code overhead. Or you can use something like scalaz's monadic function composition to avoid call site and definition site code overhead for passing config around. It is sometimes called the Reader monad, but it is simply for-comprehension enabled composition of 1-argument functions.

Slick 2.2 will ship with something like that out-of-the-box and make what you want very easy.

Also, here is an approach I am currently playing around with, a composable configuration object "TMap". This code example shows step by step how you get from global imports over parameterized functions and making them implicit to using TMap and removing most boilerplate: https://github.com/cvogt/slick-action/blob/0.1/src/test/scala/org/cvogt/di/TMapTest.scala#L49

Upvotes: 3

Related Questions