Skip to content

Instantly share code, notes, and snippets.

@chrisjwwalker
Last active September 27, 2017 09:13
Show Gist options
  • Save chrisjwwalker/c1c333356c58c5daa87f18b614bc8236 to your computer and use it in GitHub Desktop.
Save chrisjwwalker/c1c333356c58c5daa87f18b614bc8236 to your computer and use it in GitHub Desktop.
How to make readable integration tests for a Scala play application with wire mock
GET /get-user-account/:sessionId controllers.UserAccountController.getUserAccount(sessionId)
class GetUserAccountISpec extends IntegrationUtils {
"/get-user-account/:sessionId" should {
"return an OK" when {
"a user exists, they are authorised and their user id has been fetched" in {
given
.mongo.hasExistingUser
.user.isAuthorised
.session.getUserId
val response = callUrl(s"http://$wireMockHost:$wireMockPort/get-user-account/session-id-12345").get
whenReady(response) { resp =>
resp.status shouldBe OK //200
resp.body shouldBe Json.parse(
"""
|{
| "userId" : "user-id-123456",
| "name" : "Bob Ross",
| "email" : "[email protected]"
|}
""".stripMargin
)
}
}
}
}
}
trait IntegrationUtils extends WireMockSetup with WireMockOutboundCalls with UnitSpec with ScalaFutures {
class PreconditionBuilder {
implicit val builder: PreconditionBuilder = this
def mongo = Mongo()
def user = User()
def session = SessionStore()
}
def given = new PreconditionBuilder
case class Mongo()(implicit builder: Preconditionbuiler) {
def hasExistingUser: PreconditionBuilder = {
//Code to insert user into mongo
}
}
case class User()(implicit builder: PreconditionBuilder) {
def isAuthorised: PreconditionBuilder = {
//Wire mock stubbed GET to determine if user is authorised
builder
}
}
case class SessionStore()(implicit builder: PreconditionBuilder) {
def getUserId: PreconditionBuilder {
//Wire mock stubbed get users id
builder
}
}
}

How to build readable integration tests in a scala play application

A quick unit v integration testing

When we unit test an application we are testing its components. This means that one test will test one component in an class/object/trait and its outcomes. If a component utilises other components of the application we would mock then out using something like Mockito.

Integration testing on the other hand will wire the application components together and tests that the application works as a whole. We still want to only test the application though. Therefore we would mock out calls to external services using something like wiremock.

What shall we integration test?

In this gist we have a slice of scala play application defined; a routes file, a controller and a mongo repository. We want to make sure that this slice of the application can have the url called and from that return a 200 status code with a json body by calling down through the route, the controller, repository and through to mongodb.

How can we make our integration tests readable?

If we take our application slice we can try and decompose it into a set of preconditions. Looking at the code comments in UserAccountRespository.scala we can see that...

  • There is a call out to an external service to ensure the user is authorised
  • There is another call out to get the users id based on their current session id

Since we are looking at a happy scenario we can also say that the user needs to exist in the database before any of this can happen.

If translated into Gherkin...

  Feature: A call has been made from the frontend to fetch details of the user by retrieving their user account
    Scenario: Call has been made to get the users account in json
      Given the user exists in the database
      And the user is authorised
      And the users id has been fetched
      Then the response has a 200 (Ok) status
      And the response body contains the users account details

To make our test understandable and maintainable we are going to capture this gherkin in scala.

Looking at IntegrationUtils.scala we can see a trait that has been setup to include WireMock components and UnitSpec. The special part of this trait is the PreconditionBuilder class.

The PreconditionBuilder class is going to create something that resembles Gherkin syntax and therefore make our integration tests readable. The other important part about this class is the implicit builder on the first line of the class. This val is what lets us create a precondition statement.

Lets get started...

Precondition 1 says a user must exist in the database before the action can run successfully. In the utils trait there is a case class called Mongo that takes an implicit builder of type PreconditionBuilder. Contained within this case class there is a function defined as hasExistingUser. This function would contain code to actually insert a user in json format into the database. An important note is that the function returns a the implicit builder, this is so we can link preconditions together. Finally a function defined as mongo has been created in the PreconditionBuilder class that created the Mongo case class.

Precondition 2 says a user must be authorised to perform this action. This is setup in an identical way except that the case class is called User, the function inside the case class is called isAuthorised and the function in PreconditionBuilder is called user and that returns the User case class. Another difference is that following preconditions will use WireMocks stubbed outbound calls to determine if the user is authorised and that the user is has been fetched.

Precondition 3 says the users id has been fetched. This time the case class is called SessionStore the function contained with is called getUserId and the function in PreconditionBuilder is called session

The final part of this PreconditonBuilder jigsaw is the function called given and this where our precondition statement starts.

With this, the Gherkin syntax featured can be captured in scala as...

  given
    .mongo.hasExistingUser
    .user.isAuthorised
    .session.getUserId

The next step is actually call the apps get user account url and make assertions based on the scenario you're testing. To do this you can use whenReady contained with the ScalaFutures trait. An example of this usage can been seen in the GetUserAccountISpec in this gist.

Closing notes

When using WireMock to stub outbound calls, the stubbed get, post, put and patch commands can get very lengthy depending on what json you're getting or posting. This way of building integration tests means that you can reduce how much code is in your test therefore making them readable and understable. It also means that you can easily reuse database calls and WireMock stubbed calls as they're contained within the PreconditionBuilder code and not the body of your test.

@Singleton
class UserAccountController @Inject()(userAccountRepo: UserAccountRepository) extends Controller with AuthorisationUtils {
def getUserAccount(sessionId: String): Action[AnyContent] = Action.async { implicit request =>
isUserAuthorised(sessionId: String) { //Calls out to external service to see if current user is authorised
getUserIdFromSession(sessionId: String) { userId => //Calls out to external service to get the user
userAccountRepo.getUserById(userId) map { userAccount => //Calls mongo
Ok(userAccount)
} recover {
case _: MissingAccountException => NotFound
}
}
}
}
}
@Singleton
class UserAccountRepository @Inject()() extends MongoDatabase("user-accounts") {
private def userIdQuery(userId: String): BSONDocument = BSONDocument("userId" -> userId)
def getUserById(userId: String): Future[JsValue] = {
collection flatMap {
_.find(userIdQuery(userId)).one[JsValue] map {
case Some(acc) => acc
case None => throw new MissingAccountException(s"Cannot find account matching userId $userId")
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment