# How to Write End-to-End Tests for MongoDB Realm Serverless Apps

As of June 2022, the functionality previously known as MongoDB Realm is now named Atlas App Services. Atlas App Services refers to the cloud services that simplify building applications with Atlas – Atlas Data API, Atlas GraphQL API, Atlas Triggers, and Atlas Device Sync. Realm will continue to be used to refer to the client-side database and SDKs. Some of the naming or references in this article may be outdated.

End-to-end tests are the cherry on top of a delicious ice cream sundae
of automated tests. Just like many people find cherries to be disgusting
(rightly so—cherries are gross!), many developers are not thrilled to
write end-to-end tests. These tests can be time consuming to write and
difficult to maintain. However, these tests can provide development
teams with confidence that the entire application is functioning as
expected.

Automated tests are like a delicious ice cream sundae.

Today I'll discuss how to write end-to-end tests for apps built using
MongoDB Realm.

This is the third post in the *DevOps + MongoDB Realm Serverless
Functions = 😍* blog series. I began the series by introducing the
Social Stats app, a serverless app I built using MongoDB Realm. I've
explained
how I wrote unit tests
and integration tests
for the app. If you haven't read
the first post where I explained what the app does and how I architected it,
I recommend you start there and then return to this post.

>
>
>Prefer to learn by video? Many of the concepts I cover in this series
>are available in this video.
>
>

## Writing End-to-End Tests for MongoDB Realm Serverless Apps

Today I'll focus on the top layer of the testing
pyramid:
end-to-end tests. End-to-end tests work through a complete scenario a
user would take while using the app. These tests typically interact with
the user interface (UI), clicking buttons and inputting text just as a
user would. End-to-end tests ultimately check that the various
components and systems that make up the app are configured and working
together correctly.

Because end-to-end tests interact with the UI, they tend to be very
brittle; they break easily as the UI changes. These tests can also be
challenging to write. As a result, developers typically write very few
of these tests.

Despite their brittle nature, having end-to-end tests is still
important. These tests give development teams confidence that the app is
functioning as expected.

### Sidenote

I want to pause and acknowledge something before the Internet trolls
start sending me snarky DMs.

This section is titled *writing end-to-end tests for MongoDB Realm
serverless apps*. To be clear, none of the approaches I'm sharing in
this post about writing end-to-end tests are specific to MongoDB Realm
serverless apps. When you write end-to-end tests that interact with the
UI, the underlying architecture is irrelevant. I know this. Please keep
your angry Tweets to yourself.

I decided to write this post, because writing about only two-thirds of
the testing pyramid just seemed wrong. Now let's continue.

### Example End-to-End Test

Let's walk through how I wrote an end-to-test for the Social Stats app.
I began with the simplest flow:

1.  A user navigates to the page where they can upload their Twitter
    statistics.
2.  The user uploads a Twitter statistics spreadsheet that has stats for
    a single Tweet.
3.  The user navigates to the dashboard so they can see their
    statistics.

I decided to build my end-to-end tests using Jest
and Selenium. Using Jest was a
straightforward decision as I had already built my unit and integration
tests using it. Selenium has been a popular choice for automating
browser interactions for many years. I've used it successfully in the
past, so using it again was an easy choice.

I created a new file named `uploadTweetStats.test.js`. Then I started
writing the typical top-of-the-file code.

I began by importing several constants. I imported the MongoClient so
that I would be able to interact directly with my database, I imported
several constants I would need in order to use Selenium, and I imported
the names of the database and collection I would be testing later.

``` javascript
const { MongoClient } = require('mongodb');

const { Builder, By, until, Capabilities } = require('selenium-webdriver');

const { TwitterStatsDb, statsCollection } = require('../constants.js');
```

Then I declared some variables.

``` javascript
let collection;
let mongoClient;
let driver;
```

Next, I created constants for common XPaths I would need to reference
throughout my tests.
XPath
is a query language you can use to select nodes in HTML documents.
Selenium provides a variety of
ways—including
XPaths—for you to select elements in your web app. The constants below
are the XPaths for the nodes with the text "Total Engagements" and
"Total Impressions."

``` javascript
const totalEngagementsXpath = "//*text()='Total Engagements']";
const totalImpressionsXpath = "//*[text()='Total Impressions']";
```

Now that I had all of my top-of-the-file code written, I was ready to
start setting up my testing structure. I began by implementing the
[beforeAll()
function, which Jest runs once before any of the tests in the file are
run.

Browser-based tests can run a bit slower than other automated tests, so
I increased the timeout for each test to 30 seconds.

Then,
just as I did with the integration tests, I
connected directly to the test database.

``` javascript
beforeAll(async () => {
   jest.setTimeout(30000);

   // Connect directly to the database
   const uri = `mongodb+srv://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}@${process.env.CLUSTER_URI}/test?retryWrites=true&w=majority`;
   mongoClient = new MongoClient(uri);
   await mongoClient.connect();
   collection = mongoClient.db(TwitterStatsDb).collection(statsCollection);
});
```

Next, I implemented the
beforeEach()
function, which Jest runs before each test in the file.

I wanted to ensure that the collection the tests will be interacting
with is empty before each test, so I added a call to delete everything
in the collection.

Next, I configured the browser the tests will use. I chose to use
headless Chrome, meaning that a browser UI will not actually be
displayed. Headless browsers provide many
benefits
including increased performance. Selenium supports a variety of
browsers,
so you can choose to use whatever browser combinations you'd like.

I used the configurations for Chrome when I created a new
WebDriver
stored in `driver`. The `driver` is what will control the browser
session.

``` javascript
beforeEach(async () => {
   // Clear the database
   const result = await collection.deleteMany({});

   // Create a new driver using headless Chrome
   let chromeCapabilities = Capabilities.chrome();
   var chromeOptions = {
      'args': '--headless', 'window-size=1920,1080']
   };
   chromeCapabilities.set('chromeOptions', chromeOptions);
   driver = new Builder()
      .forBrowser('chrome')
      .usingServer('http://localhost:4444/wd/hub')
      .withCapabilities(chromeCapabilities)
      .build();
});
```

I wanted to ensure the browser session was closed after each test, so I
added a call to do so in
[afterEach().

``` javascript
afterEach(async () => {
   driver.close();
})
```

Lastly, I wanted to ensure that the database connection was closed after
all of the tests finished running, so I added a call to do so in
afterAll().

``` javascript
afterAll(async () => {
   await mongoClient.close();
})
```

Now that I had all of my test structure code written, I was ready to
begin writing the code to interact with elements in my browser. I
quickly discovered that I would need to repeat a few actions in multiple
tests, so I wrote functions specifically for those.

-   refreshChartsDashboard():
    This function clicks the appropriate buttons to manually refresh the
    data in the dashboard.
-   moveToCanvasOfElement(elementXpath):
    This function moves the mouse to the chart canvas associated with
    the node identified by `elementXpath`. This function will come in
    handy for verifying elements in charts.
-   verifyChartText(elementXpath,
    chartText):
    This function verifies that when you move the mouse to the chart
    canvas associated with the node identified by `elementXpath`, the
    `chartText` is displayed in the tooltip.

Finally, I was ready to write my first test case that tests uploading a
CSV file with Twitter statistics for a single Tweet.

``` javascript
test('Single tweet', async () => {
   await driver.get(`${process.env.URL}`);
   const button = await driver.findElement(By.id('csvUpload'));
   await button.sendKeys(process.cwd() + "/tests/ui/files/singletweet.csv");

   const results = await driver.findElement(By.id('results'));
   await driver.wait(until.elementTextIs(results, `Fabulous! 1 new Tweet(s) was/were saved.`), 10000);

   const dashboardLink = await driver.findElement(By.id('dashboard-link'));
   dashboardLink.click();

   await refreshChartsDashboard();

   await verifyChartText(totalEngagementsXpath, "4");
   await verifyChartText(totalImpressionsXpath, "260");
})
```

Let's walk through what this test is doing.

Screen recording of the Single tweet test when run in Chrome

The test begins by navigating to the URL for the application I'm using
for testing.

Then the test clicks the button that allows users to browse for a file
to upload. The test selects a file and chooses to upload it.

The test asserts that the page displays a message indicating that the
upload was successful.

Then the test clicks the link to open the dashboard. In case the charts
in the dashboard have stale data, the test clicks the buttons to
manually force the data to be refreshed.

Finally, the test verifies that the correct number of engagements and
impressions are displayed in the charts.

After I finished this test, I wrote another end-to-end test. This test
verifies that uploading CSV files that update the statistics on existing
Tweets as well as uploading CSV files for multiple authors all work as
expected.

You can find the full test file with both end-to-end tests in
storeCsvInDB.test.js.

## Wrapping Up

You now know the basics of how to write automated tests for Realm
serverless apps.

The Social Stats application source code and associated test files are
available in a GitHub repo:
. The repo's readme
has detailed instructions on how to execute the test files.

While writing and maintaining end-to-end tests can sometimes be painful,
they are an important piece of the testing pyramid. Combined with the
other automated tests, end-to-end tests give the development team
confidence that the app is ready to be deployed.

Now that you have a strong foundation of automated tests, you're ready
to dive into automated deployments. Be on the lookout for the next post
in this series where I'll explain how to craft a CI/CD pipeline for
Realm serverless apps.

## Related Links

Check out the following resources for more information:

-   GitHub Repository: Social
    Stats
-   Video: DevOps + MongoDB Realm Serverless Functions =
    😍
-   Documentation: MongoDB Realm
-   MongoDB Atlas
-   MongoDB Charts