Testing Asynchronous Code
Learn how to test asynchronous code in Scala with ScalaTest.
Synchronous vs. asynchronous code
So far, we’ve only been testing synchronous code; every method and test we’ve written has been blocking and not asynchronous. This isn’t very realistic in a modern application since many operations have to be non-blocking. Think of downloading a file from somewhere, querying a database, or waiting for a response from another application on the internet. If those operations were blocking, our applications would be incredibly slow, and we’d waste tons of time waiting for results.
The basic concurrency component in Scala is the Future
class. An instance of Future
is nothing more than a placeholder for a value that will be ready sometime in the future. Using Future
leads to faster code that can be run in a non-blocking way. In this lesson and the next, we’ll get a glimpse of how to test asynchronous code in Scala and ScalaTest. Covering the Future
execution model in ScalaTest in detail is outside the scope of this course because it requires a deeper understanding of the concurrency model in Scala. In the next sections, we’ll see different possibilities for Future
in our tests without focusing too much on the underlying mechanics.
Note: The
Future
class is sometimes considered a low-level construct that shouldn’t be used directly in an application. We tend to agree, and if you can, you should use higher-level concurrent libraries in your code rather than reinventing the wheel. However, these libraries are beyond the scope of this course, so we’ll focus onFuture
here.
First, we’ll define a new repository named CourseRepository
, which is supposed to load the details of the courses.
import mdipirro.educative.io.effectiveunitandintegrationtestinginscala.model.v2.Courseimport scala.concurrent.{ExecutionContext, Future}trait CourseRepository:def getAll: Future[Seq[Course]]class SimpleCourseRepository(using ExecutionContext):def getAll: Future[Seq[Course]] = Future { Seq.empty }
The CourseRepository
is a trait defining just one method, getAll
, returning a Seq[Course]
. On the other hand, SimpleCourseRepository
is just a dummy implementation of that trait, returning an empty sequence of the Course
class. The SimpleCourseRepository
class’s override of getAll
wouldn’t actually need to return a Future
since it returns a constant value, but let’s assume that Seq.empty
is a time-consuming operation involving querying a database.
Note that SimpleCourseRepository
requires an ExecutionContext
to create a Future
. An ExecutionContext
in Scala is a way to specify how program logic should be executed at runtime.
Testing Future
while blocking
The simplest way for us to test a portion of asynchronous code is blocking as usual. In Scala we can explicitly wait for the result of a Future
using Await
, a Scala object
exposing two methods: ready
...