> For the complete documentation index, see [llms.txt](https://book.restfulnode.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.restfulnode.com/part-3/chapter-10/4-testing-the-domain.md).

# Testing the Domain

### Repository

Recall how we implemented the `userRepository` in one of the earlier chapters.

*File: src/domain/repositories/user.repository.js*

```javascript
const UserModel = require('../models/user.model')

/**
 *
 * @param {*} user {
 *  - name
 *  - email
 *  - password
 * }
 *
 * @returns user
 */
const createUser = async (userData) => {
  const user = new UserModel(userData)
  const userReturn = await user.save(userData)

  return userReturn
}

module.exports = {
  createUser
}
```

Now it's time to write out test cases.

For testing repositories, what we are mainly interested in is if the database operations are working. For that to work, we'll have to arrange to connect to the database on every test.

Here we have a couple of helpers that will help us test for database-based test cases. The `db` function let's us create a connection to the database, it's simply a wrapper around the mongoose database driver, that is why you see a `disconnect` function in the `afterAll` function. We then have a `dbTestUtils` object that has a `clearDatabase` function which allows us to reset the database and let's us start off from a clean slate. For a closer look at what those helper functions do, you can dive deeper by looking at the source code of the repository.

*File: src/domain/repositories/\_\_tests\_\_/user.repository.test.js*

```javascript
const db = require('../../../utils/db')
let dbConnection
const dbTestUtils = require('../../../../tests/testUtils/dbTestUtil')

const userRepository = require('../user.repository')

beforeAll(async () => {
  dbConnection = await db()
})

afterEach(async () => {
  await dbTestUtils.clearDatabase()
})

afterAll(async () => {
  await dbConnection.disconnect()
})

describe('Test Suite: User Repository', () => {
  // Our tests go here...
})
```

Now Moving on to the first successful test case, we have the following. We are simply calling the `createUser` method and passing in all of the necessary fields. This is a nice, clean, and simple test.

```javascript
test('User Repository - createUser - success', async () => {
  const testUser = {
    first_name: 'Yichen',
    last_name: 'Zhu',
    email: 'yichen@yichen.com',
    password: 'password123',
    phone_number: '1234567890'
  }

  const user = await userRepository.createUser(testUser)
  const expectedName = 'Yichen'
  const actual = user.first_name

  expect(actual).toEqual(expectedName)
})
```

For the failing test case, we can decide to not pass in certain required fields. Recall that our UserModel had certain required fields when we created it.

Here is what a possible failing test case would look like.

```javascript
test('User Repository - createUser - error', async () => {
  try {
    const testUser = {
      email: 'yichen@yichen.com'
    }

    await userRepository.createUser(testUser)
  } catch (error) {
    /* eslint-disable-next-line */
    expect(error.message).toBe(
      'user validation failed: phone_number: Path `phone_number` is required., password: Path `password` is required., first_name: Path `first_name` is required.'
    )
  }
})
```

Although this seems a little redundant due to the fact that we already have validation in another layer, this is simply to illustration how one might go about writing tests for a repository layer. When you start creating more complicated repositories that use multiple different models, you'll find more complicated tests are needed.

### Service

Recall how we implemented the `authService` in one of the earlier chapters.

*File: src/domain/services/auth.service.js*

```javascript
const ApiException = require('../../utils/ApiException')
const userRepository = require('../repositories/user.repository')

/**
 * @returns User
 */
const registerUser = async (user) => {
  try {
    const createdUser = await userRepository.createUser(user)
    return createdUser
  } catch (err) {
    if (err.code === 11000 && err?.keyPattern?.email === 1) {
      throw new ApiException({
        message: `This email is already taken.`,
        status: 'error',
        code: 409,
        data: null,
        errors: [`This email is already taken.`]
      })
    }
  }
}
```

The service layer is going to be all about us mocking out the repository layer. We do so in order to keep the tests as separate and isolated as possible.

For a successful test case, take a look at the following. We do a simple mock of both the `userRepository` and its method `createUser` functions and see if they've been called properly.

*File: src/domain/services/\_\_tests\_\_/auth.service.test.js*

```javascript
const userRepository = require('../../repositories/user.repository')
const authService = require('../auth.service')

beforeAll(async () => {})

beforeEach(() => {
  userRepository.createUser = jest.fn(() => {
    return {}
  })
})

afterEach(async () => {})

afterAll(async () => {})

describe('Test Suite: Auth Service', () => {
  test('Auth Service - registerUser', async () => {
    const testUser = {
      first_name: 'john',
      last_name: 'doe',
      email: 'john@john.com',
      password: 'password',
      phone_number: '4168561988'
    }
    await authService.registerUser(testUser)

    expect(userRepository.createUser).toHaveBeenCalledWith(testUser)
    expect(userRepository.createUser).toHaveBeenCalledTimes(1)
    expect(userRepository.createUser).toHaveReturnedWith({})
  })
})
```

For the failing test, we'll simply want to throw an `ApiException` artificially.

```javascript
test('Auth Service - registerUser - error', async () => {
  userRepository.createUser = jest.fn(() => {
    class CustomError extends Error {
      constructor() {
        super()
        this.code = 11000
        this.keyPattern = {
          email: 1
        }
      }
    }

    throw new CustomError()
  })

  try {
    await authService.registerUser({})
  } catch (err) {
    /* eslint-disable-next-line */
    expect(err.message).toBe('This email is already taken.')
  }
})
```
