ScalaTest
async programming
fixture-context
testing
Scala

How to use fixture-context objects with async specs in ScalaTest?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Async tests in ScalaTest are easiest to maintain when setup and teardown are centralized in fixture helpers. A fixture-context object bundles test dependencies such as repositories, services, or fake clients so each test focuses only on behavior. With async specs, the fixture helper should return Future[Assertion] and manage cleanup after future completion.

Pick an Async Style That Fits Your Suite

ScalaTest provides async variants such as AsyncFlatSpec, AsyncFunSuite, and AsyncWordSpec. All of them work with fixture-context patterns. For many teams, AsyncFlatSpec is a good balance between readability and concise syntax.

scala
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test

Build a Fixture Context Object

Create a small context container with dependencies required by each test.

scala
1import scala.concurrent.{ExecutionContext, Future}
2
3final class InMemoryRepo {
4  private var values: Vector[String] = Vector.empty
5  def save(v: String): Future[Unit] = Future.successful {
6    values = values :+ v
7  }
8  def all(): Future[Vector[String]] = Future.successful(values)
9  def close(): Future[Unit] = Future.successful(())
10}
11
12final class UserService(repo: InMemoryRepo)(using ExecutionContext) {
13  def createUser(name: String): Future[Unit] = repo.save(name)
14  def listUsers(): Future[Vector[String]] = repo.all()
15}

This keeps dependencies explicit and test setup deterministic.

Wrap Fixture Setup and Teardown in a Helper

With async tests, cleanup should happen after the test future completes, whether success or failure.

scala
1import org.scalatest.Assertion
2import org.scalatest.flatspec.AsyncFlatSpec
3import org.scalatest.matchers.should.Matchers
4
5import scala.concurrent.{ExecutionContext, Future}
6
7class UserServiceSpec extends AsyncFlatSpec with Matchers {
8
9  given ExecutionContext = scala.concurrent.ExecutionContext.global
10
11  final case class TestContext(repo: InMemoryRepo, service: UserService)
12
13  def withContext(testCode: TestContext => Future[Assertion]): Future[Assertion] = {
14    val repo = new InMemoryRepo
15    val service = new UserService(repo)
16    val ctx = TestContext(repo, service)
17
18    testCode(ctx).transformWith { result =>
19      repo.close().transform(_ => result)
20    }
21  }
22
23  "UserService.createUser" should "persist created users" in withContext { ctx =>
24    for {
25      _ <- ctx.service.createUser("Ava")
26      _ <- ctx.service.createUser("Noah")
27      users <- ctx.service.listUsers()
28    } yield {
29      users should contain allOf ("Ava", "Noah")
30    }
31  }
32}

This pattern ensures cleanup runs for both passed and failed tests.

Compose Multiple Fixtures Without Boilerplate

If tests need more resources such as fake HTTP server and temporary directory, include them in one context case class and initialize in one place. Keep constructor side effects minimal so setup failures are easy to diagnose.

For expensive dependencies, prefer per-suite shared fixtures only when isolation requirements allow it. Otherwise per-test fixtures reduce flaky cross-test interactions.

Async Timeouts and Execution Context

Async specs rely on an execution context. Use a deterministic context policy and avoid blocking operations on the same pool used by futures. If tests involve database clients or network calls, configure test patience or timeouts intentionally so failures are informative.

If your code uses blocking APIs, wrap blocking sections with dedicated thread pools instead of default global pool.

FixtureAsync Style Alternative

ScalaTest also provides fixture-oriented async styles where fixture type is part of suite signature. Those styles can reduce wrapper function usage in large suites, but the explicit helper approach shown above is often easier for teams migrating from non-fixture async tests.

Choose one style for consistency across repository tests.

Common Pitfalls

A common pitfall is creating fixtures in the test body and forgetting teardown on failed futures, which leaks resources across runs. Another issue is mixing blocking Await.result calls into async specs, which can deadlock or slow the suite dramatically. Teams also share mutable fixtures across async tests unintentionally, causing order-dependent failures. Finally, noisy global execution context usage can hide starvation issues, so monitor test runtime behavior under parallel execution.

Summary

  • Use async ScalaTest suites with fixture-context helpers for clean dependency setup.
  • Return Future[Assertion] and run teardown after completion.
  • Keep fixture objects explicit and lightweight for better readability.
  • Avoid blocking calls inside async tests.
  • Standardize one fixture pattern across the codebase to reduce flakiness.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.