playframework / play-mailer

Play mailer plugin
Apache License 2.0
249 stars 74 forks source link

Testing that an email is sent #73

Open drhumlen opened 9 years ago

drhumlen commented 9 years ago

Is there a way to test that an email is sent, and check the contents of it? Would be nice to do something like:

"A verification email is sent when the user has registered" in {
  MailPlugin.startCapturing()
  UserModule.registerNewUser(name = "Frodo", email = "frodo@example.com")
  val emails: List[Email] = MailPlugin.stopCapturingAndCollectResults()

  emails mustEqual List(
    Email(
      subject = "Welcome Frodo",
      recipient = "frodo@example.com",
      bodyText = "Here is your verifcation link: ______"
    )
  )
}

Or perhaps something more "functional"/safe:

"A verification email is sent when the user has registered" in {
  val emails: List[Email] = MailPlugin.captureMailsSentInBlock {
    UserModule.registerNewUser(name = "Frodo", email = "frodo@example.com")
  }

  emails mustEqual List(
    Email(
      subject = "Welcome Frodo",
      recipient = "frodo@example.com",
      bodyText = "Here is your verifcation link: ______"
    )
  )
}

Is it possible to do something like this at all right now? ie. Setting the plugin in testing/capture mode so that we can verify: (1) that en email is sent, (2) verify the contents of it.

Also, mock-mode must be on when running your test suite, right? (play.mailer.mock = yes)

I'm sorry if this is asked before, but I couldn't find anything in the manual or from searching.

ggrossetie commented 9 years ago

Hello,

If you are using Play 2.4 then you can use dependency injection to provide a mock of MailerClient to your UserModule.

With Guice (runtime dependency injection):

val application = new GuiceApplicationBuilder()
  .overrides(bind[MailerClient].to[MockMailerClient])
  .build

https://www.playframework.com/documentation/2.4.x/ScalaTestingWithGuice

You can create a mock of MailerClient using Mockito or ScalaMock. Then you can assert that sendEmail method was called with the expected Email object, something like:

"A verification email is sent when the user has registered" in new WithApplication(application) {
  UserModule.registerNewUser(name = "Frodo", email = "frodo@example.com")
  Mockito.verify(UserModule.mailerClient).sendEmail(Email(
      subject = "Welcome Frodo",
      recipient = "frodo@example.com",
      bodyText = "Here is your verifcation link: ______"
    ))
}

Also, mock-mode must be on when running your test suite, right? (play.mailer.mock = yes)

No play.mailer.mock should be considered deprecated because you can now choose to inject a MockMailer with the qualifier "mock", see: https://github.com/playframework/play-mailer/blob/master/src/main/scala/play/api/libs/mailer/MailerPlugin.scala#L25

Also in your test you want to do assertion. The MockMailer in play-mailer will just prints out the email data, see: https://github.com/playframework/play-mailer/blob/master/src/main/scala/play/api/libs/mailer/MailerPlugin.scala#L248

I'm sorry if this is asked before, but I couldn't find anything in the manual or from searching.

Don't apologize this is a very good question and I need to add documentation about testing. If something is not clear, feel free to ask again.

drhumlen commented 9 years ago

Thanks for swift reply :)

Unfortunately, Play's support for dependency injection was a little too late for our project :( . We're already 2.5 years into a quite big play-project, so we don't have time to rewrite everything to use dependency injection.

I would go for the solution you suggested above though when/if we start another play-project, but for now I made a small wrapper to work with our existing code:

import play.api.Play.current
import play.api.libs.mailer.{Email, MailerPlugin}

object Mailer {
  private var capturedEmails: List[Email] = Nil
  private var captureMode = false

  def send(email: Email): Unit = {
    if (captureMode == true) {
      capturedEmails = email :: capturedEmails
    } else {
      MailerPlugin.send(email)
    }
  }

  def captureMailsSentIn(blk: => Unit): List[Email] = {
    captureMode = true
    capturedEmails = Nil
    blk
    captureMode = false
    capturedEmails
  }
}

It's not exactly 100% functional, but it does what must be done in order to test it. If anybody else is in the same situation as us, it's probably a good idea to set play.mailer.mock=yes since all code that is not run directly inside captureMailsSentIn will still send emails around to god-knows-who.

Anyway, just thought I'd post this here in case someone else has painted themselves into a DI-less-corner like we have. Here's how we use it in one of our tests:

    "a login-token is sent on email when registering" in {
      val capturedEmails = Mailer.captureMailsSentIn {
        Helpers.reqTest(
          routes.ApplicantController.registerForeign(),
          input = ForeignRegistrationFormData(
            name = "Frodo",
            birthDate = DateTime.now,
            email = "frodo@example.com",
            mobile = "12345678",
            gender = Gender.Male
          ),
          output = Json.obj("id" -> JsonMatcher.___anyString),
          expectedStatusCode = CREATED
        )
      }

      capturedEmails must haveLength(1)
      val mail = capturedEmails(0)

      mail.to === Seq("frodo@example.com")
      mail.bodyText.get must be containing "http://localhost:9000/api/verify?code="
    }

(Edit: remember to replace every usage of the singleton MailerPlugin.send with Mailer.send)

ggrossetie commented 9 years ago

Hello @drhumlen, thanks for sharing your findings. I think you can also use ScalaMock because its allow to mock singleton and companion objects. So I think you can do something like:

val mailerPluginMock = mockObject(MailerPlugin)
UserModule.registerNewUser(name = "Frodo", email = "frodo@example.com")
val expectedEmail = Email(
  subject = "Welcome Frodo",
  recipient = "frodo@example.com",
  bodyText = "Here is your verifcation link: ______"
)
mailerPluginMock.expects.sendEmail(expectedEmail)

The benefit is that you don't have to modify your code.

marcospereira commented 7 years ago

Hey,

The way I would recommend that is to have a separated configuration for your test environment, having just the email settings change, and then use something like Wiser or GreenMail which are both made to test email sending.

wsargent commented 7 years ago

Hi all -- placebocurejava has been banned from playframework, as he was posting on the google group with stolen credentials. His comments have been removed. Please see https://groups.google.com/forum/#!topic/play-framework/5FCht0abCI4 for more information.

threeel commented 5 years ago

@drhumlen maybe you can use something like this https://github.com/mailhog/MailHog have it as docker and use its API to do your test.