Skip to Content
Course content

95: Mocking with ScalaMock

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've probably realized that unit testing becomes a nightmare when your code depends on external systems—databases, third-party APIs, or slow legacy services. You don't want your test suite to fail just because the Stripe API is down or because your local database isn't configured. That's where ScalaMock comes in. It lets us create "fake" versions of our dependencies so we can isolate the logic we're actually trying to test.

How do I actually create a mock and define its behavior?

The core of ScalaMock is the mock[T] method. You use it to create an instance of a trait or class that behaves exactly how you tell it to. Let's say we're building a PaymentProcessor that depends on a PaymentGateway. We don't want to charge real credit cards during a test, obviously.

trait PaymentGateway {
  def charge(amount: Double, currency: String): Boolean
}

class PaymentProcessor(gateway: PaymentGateway) {
  def processPayment(amount: Double): String = {
    if (gateway.charge(amount, "USD")) "Success" else "Failed"
  }
}

// In your test:
val mockGateway = mock[PaymentGateway]
val processor = new PaymentProcessor(mockGateway)

// Tell the mock: "When charge is called with 100.0 and 'USD', return true"
(mockGateway.charge _).expects(100.0, "USD").returning(true)

val result = processor.processPayment(100.0)
assert(result == "Success")

Notice the (mockGateway.charge _) syntax. We're passing the method itself as a function to ScalaMock. I'll be honest: the first time I saw that syntax, it looked like a typo. But it's how ScalaMock intercepts the call to set up the expectation.

What if I don't care about the exact arguments passed in?

In the example above, the test would fail if the PaymentProcessor suddenly decided to charge 100.01 instead of 100.0. Sometimes, that's too rigid. If you only care that some double and some string were passed, you can use argument matchers like where or arg.

import org.scala-mock.ScalaMockMatchers._

// Accept any double and any string
(mockGateway.charge _).expects(where { (amount: Double) => amount > 0 }, where { (curr: String) => curr == "USD" }).returning(true)

// Or use a simpler wildcard if you really don't care
(mockGateway.charge _).expects(*, *).returning(true)

I usually recommend being as specific as possible. If you use * everywhere, you might miss a bug where your code is passing null or a negative number to your dependency. Use where when you have a specific condition to validate.

What is the difference between "expects" and "stub"?

This is a common point of confusion. expects is for verification. If you use expects and that method is never called during the test, ScalaMock will throw an exception and fail your test. It's saying, "I expected this to happen, but it didn't."

A stub, on the other hand, is just for behavior. Use a stub when the method call is necessary for the code to run, but you don't actually care if it gets called or how many times it's called. It just needs to return a value so the rest of the logic can proceed.

// This MUST be called exactly once, or the test fails
(mockGateway.charge _).expects(100.0, "USD").returning(true)

// This CAN be called, and if it is, it returns true. If not, no one cares.
(mockGateway.charge _).stub(where { (a: Double) => true }, where { (s: String) => true }).returns(true)

I generally use expects for the primary action I'm testing (the "side effect") and stub for the supporting data the system needs to function. It keeps the test failures focused on what actually matters.




📋 Practical Task

Exercise: Mocking a User Notification Workflow

You are working on a UserService that handles user registration. The service depends on a UserRepository to save the user and an EmailClient to send a welcome email.

The Goal: Write a test case using ScalaMock to ensure that the EmailClient is only called if the UserRepository successfully saves the user.

Requirements:

  1. Create a trait UserRepository with a method save(user: User): Boolean.
  2. Create a trait EmailClient with a method sendWelcomeEmail(email: String): Unit.
  3. Implement a UserService class that calls repo.save(user). If it returns true, it should then call emailClient.sendWelcomeEmail(user.email).
  4. Write a test where UserRepository.save returns false. Verify that EmailClient.sendWelcomeEmail is never called.
  5. Write a test where UserRepository.save returns true. Verify that EmailClient.sendWelcomeEmail is called exactly once with the correct email address.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.