Pragmatic Testing in AdonisJS with Japa #3.2

Introduction to Fakes

In This Lesson

We'll use AdonisJS Fakes in Japa for isolated testing of service dependencies (like email). Learn to put the mail service in fake mode, call the service method, use built-in assertions, and ensure proper cleanup.

Created by
@tomgobich
Published

Notes Used to Craft this Lesson

AdonisJS has a ton of built-in support for Fakes, making testing various modules super convenient.

Fakes are encapsulated working versions of a real dependency. For example, if we use a mail fake, all the APIs will work the same, but the result will be encapsulated in-memory rather than actually sending an email.

AdonisJS's built-in fakes also come with specialized assertions we can use, making things even easier to test.

Let's take email as an example. We have in our  UserService this sendWelcomeEmail method that emits an event and ultimately sends an email via our WelcomeEmailNotification.

export default class UserService {
  /**
   * Sends a welcome email to the user.
   * The email is sent asynchronously and will not block the execution of the current code.
   * @param data - An object containing the user's full name and email address
   * @returns A promise resolving when the email is sent
   */
  async sendWelcomeEmail(data: { fullName: string | null; email: string }) {
    await emitter.emit('mail:send_welcome_email', data)
  }
}
Copied!
  • app
  • services
  • user_service.ts

Let's write a test for this service method.

node ace make:test services/user_service --suite=unit
Copied!

Let's name our test "send welcome email."

test.group("Services user service", () => {
  test("send welcome email", async () => {});
});
Copied!

Then, we'll want to switch the mail service into its fake mode. This also kicks back to us the fake in-memory collection we can use to make assertions with.

test.group("Services user service", () => {
  test("send welcome email", async () => {
    const { mails } = mail.fake();
  });
});
Copied!

Great, next let's prepare our test and call our send method.

test.group("Services user service", () => {
  test("send welcome email", async () => {
    const { mails } = mail.fake();
    const userService = new UserService();
    const user = {
      fullName: "John Doe",
      email: "john.doe@test.com",
    };

    await userService.sendWelcomeEmail(user);
  });
});
Copied!

Now, we're ready to assert that the intended email was sent!

test.group("Services user service", () => {
  test("send welcome email", async () => {
    const { mails } = mail.fake();
    const userService = new UserService();
    const user = {
      fullName: "John Doe",
      email: "john.doe@test.com",
    };

    await userService.sendWelcomeEmail(user);

    mails.assertSentCount(1);
    mails.assertSent(WelcomeEmailNotification, ({ message }) => {
      return message.hasTo(user.email);
    });
  });
});
Copied!

Finally, we need to restore the mail service, and we can do this as part of the individual test's cleanup process. This gets run after the test has completed, similar to the hook cleanup we covered earlier.

test.group("Services user service", () => {
  test("send welcome email", async ({ cleanup }) => {
    const { mails } = mail.fake();
    cleanup(() => mail.restore());

    const userService = new UserService();
    const user = {
      fullName: "John Doe",
      email: "john.doe@test.com",
    };

    await userService.sendWelcomeEmail(user);

    mails.assertSentCount(1);
    mails.assertSent(WelcomeEmailNotification, ({ message }) => {
      return message.hasTo(user.email);
    });
  });
});
Copied!

You may think the location of the cleanup doesn't matter, but we actually want this cleanup to be registered sooner rather than later. Should our tests run into any issues while running, so long as the cleanup hook is registered, it'll run!

We'll actually see why this is important in action when we get to file upload fakes.

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!