Generic mocks

Now we have all that we need to create a generic mock implementation and mock all the dao packages. Under the location ReactiveWebStore/test/, we will create a package called mocks, and create a call, GenericMockedDao. Your GenericMockedDao.scala file should look like this:

package mocks

import models.BaseModel

class GenericMockedDao[T <: BaseModel] {

import java.util.concurrent.atomic.AtomicLong import scala.collection.mutable.HashMap import scala.concurrent._

import play.api.libs.concurrent.Execution.

Implicits.defaultContext var inMemoryDB = new HashMap[Long,T] var idCounter = new AtomicLong(0) def findAll(): Future[Seq[T]] = {

Future {

if (inMemoryDB.isEmpty) Seq() inMemoryDB.values.toSeq

}

}

def findById(id:Long): Future[Option[T]] = {

Future {

inMemoryDB.get(id)

}

}

def remove(id:Long): Future[Int] = {

Future {

validateId(id)

inMemoryDB.remove(id)

1

}

}

def insert(t:T): Future[Unit] = {

Future {

val id = idCounter.incrementAndGet(); t.setId(Some(id)) inMemoryDB.put(id, t)

Unit

}

}

def update(t:T): Future[Unit] = {

Future {

validateId(t.getId.get) inMemoryDB.put(t.getId.get, t)

Unit

}

}

private def validateId(id:Long):Unit = { val entry = inMemoryDB.get(id)

if (entry==null || entry.equals(None)) throw new RuntimeException("Could not find Product: " + id)

}

}

So the GenericMockedDao call expects the Generic parameter, which could be any class extending from BaseModel. Then we use an in-memory HashMap implementation and a counter to simulate database operations. We run all the operations inside Futures, so we don't break the new signature the code is expecting. Now we can create three MockedDaos for each model we need: product, review, and image.

Your mocks.ProductMockedDao.scala file should look like this:

package mocks

class ProductMockedDao extends IProductDao { val dao:GenericMockedDao[Product] = new GenericMockedDao[Product]()

override def findAll(): Future[Seq[Product]] = { dao.findAll()

}

override def findById(id:Long): Future[Option[Product]] = { dao.findById(id)

}

override def remove(id:Long): Future[Int] = { dao.remove(id)

}

override def insert(p:Product): Future[Unit] = { dao.insert(p)

}

override def update(p2:Product): Future[Unit] = { dao.update(p2)

}

override def toTable:TableQuery[_] = { null

}

}

As you can see here, we implement the iProdutDao trait, and we delegate all operations to genericMockedDao. Since everything is in-memory, we don't need to implement the toTable function. We need to do the same for the review and image. Your mocks.ReviewMockedDao.scala file should look like this:

package mocks

class ReviewMockedDao extends IReviewDao { val dao:GenericMockedDao[Review] = new GenericMockedDao[Review]()

override def findAll(): Future[Seq[Review]] = { dao.findAll()

}

override def findById(id:Long): Future[Option[Review]] = { dao.findById(id)

}

override def remove(id:Long): Future[Int] = { dao.remove(id)

}

override def insert(p:Review): Future[Unit] = { dao.insert(p)

}

override def update(p2:Review): Future[Unit] = { dao.update(p2)

}

override def toTable:TableQuery[_] = { null

}

}

Exactly like product, we delegate all operations to GenericMockedDao. Now let's move to the last one, the image, and then we can fix the tests. Your mocks.ImageMockedDao.scala file should look like this:

package mocks

class ImageMockedDao extends IImageDao {

val dao:GenericMockedDao[Image] = new GenericMockedDao[Image]() override def findAll(): Future[Seq[Image]] = { dao.findAll()

}

override def findById(id:Long): Future[Option[Image]] = { dao.findById(id)

}

override def remove(id:Long): Future[Int] = { dao.remove(id)

}

Persistence with Slick

override def insert(p:Image): Future[Unit] = { dao.insert(p)

}

override def update(p2:Image): Future[Unit] = { dao.update(p2)

}

override def toTable:TableQuery[_] = { null

}

}

Okay, we have all the mocks that we need for now. We can move on to fix the test specs. We need to fix services tests and controller test. Services tests will use mocks. Controllers tests, however, will use the real database implementation. We need to use other utility classes for controller tests. Located in the test source folder, we need to create a package called utils.

Your utils.DBCleaner.scala file should look like this:

package utils object DBCleaner {

val pool = Executors.newCachedThreadPool()

implicit val ec = ExecutionContext.fromExecutorService(pool) def cleanUp():Unit = {

Class.forName("com.mysql.cj.jdbc.Driver") val db = Database.forURL

("jdbc:mysql://127.0.0.1:3306/RWS_DB?useUnicode=

true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=

false&serverTimezone=UTC",

"root", "") try{

Await.result(

db.run(

DBIO.seq(

sqlu...... DELETE FROM Product; ......,

sqlu...... DELETE FROM Image; ......,

sqlu...... DELETE FROM Review; ......,

sqlu ALTER TABLE Product AUTO_INCREMENT = 1 ,

sqlu ALTER TABLE Image AUTO_INCREMENT = 1 ,

sqlu...... ALTER TABLE Review AUTO_INCREMENT = 1 ......

)

)

,20 seconds)

}catch{

case e:Exception => Unit

}

}

}

DBCleaner will connect to the real database and perform delete statements to clean up all table data. After deleting all data in the tables, we also reset the sequence in the database; otherwise, our tests will not have the predictability we need to do assertions.

As you can see in db.run, we can use DBlO.seq, which allows us to execute multiple instructions on the database. Here we are not using Scala code. We are using pure SQL statements, since we need to use very specific MySQL functions to reset the sequences.

If you need, you could use all these functions in your application. This is useful if you need to use a specific database function, if you have a very complex query, or sometimes, because there is a performance issue.

Most fixes we do now center around using Awaits to wait for the Future result, and also using our new mocks. For the controller test, we need to call the DBCleaner function as well.

 
Source