# Controller tests

We can go to the file <mark style="color:purple;">users.controller.spec</mark> to begin, and also attempt to run this test file.

```sh
yarn test:watch -- users.controller
```

But the same error will occur, as the original <mark style="color:blue;">`service`</mark> is included in the <mark style="color:blue;">`module`</mark>. Therefore, it will attempt to find the <mark style="color:blue;">`Repository`</mark> inside the <mark style="color:blue;">`module`</mark> and will <mark style="color:red;">fail</mark>. We don't want the actual <mark style="color:blue;">`service`</mark> here, let alone the actual <mark style="color:blue;">`Repository`</mark>. Only the unit that we are testing should be the focus of the test, and everything else should be mocked. Let's then fix this.

Back in the <mark style="color:purple;">testing.util</mark> file, we should create a type for a <mark style="color:blue;">`MockService`</mark>. Currently, there's nothing that uniquely identifies a <mark style="color:blue;">`service`</mark> as a **service**, apart from it being a **class**, so instead let's create a type for a <mark style="color:blue;">`MockClass`</mark>.

The <mark style="color:blue;">`MockClass`</mark> type will be a bit similar to <mark style="color:blue;">`MockRepository`</mark>, but with a difference. Here, we should use a type parameter to indicate that a class will be mocked. To enforce that, we can make it extend from the <mark style="color:blue;">`Type`</mark> interface, which represents a generic <mark style="color:blue;">constructor</mark>. There is just something we should be aware of: this type parameter will not accept the class itself, but the <mark style="color:blue;">typeof</mark> the class, which represents its constructor. Due to this, we should give to <mark style="color:blue;">`MockProxy`</mark> not the type argument itself, as it represents the constructor, but the type of its instance, by using <mark style="color:blue;">`InstanceType`</mark>. To enforce a class, unfortunately this juggling is necessary. We then get the following:

```typescript
export type MockClass<TClass extends Type> = MockProxy<InstanceType<TClass>>;
```

Back in the file <mark style="color:purple;">users.controller.spec</mark>, we can

* Create a variable for the <mark style="color:blue;">`service`</mark>, using <mark style="color:blue;">typeof</mark> as was mentioned

```typescript
let service: MockClass<typeof UsersService>;
```

* Call the function to instantiate the base mock

```typescript
useValue: createMock(),
```

* Obtain a reference to it from the <mark style="color:blue;">`module`</mark>

```typescript
service = module.get(UsersService);
```

Let's also put those previous three auxiliary functions at the bottom of the file, once again.

Very well, now the remainder will be extremely similar to the <mark style="color:blue;">`UsersService`</mark> tests. The only thing that may be a bit different is that some route handlers expect an <mark style="color:blue;">`IdDto`</mark>, so we should also have one there.

```typescript
describe('create', () => {
  describe('when no error occurs', () => {
    it('should create a user', async () => {
      const id = 1;
      const createUserDto = genCreateDto();
      const expectedUser = genUser(id, createUserDto);
  
      service.create.mockResolvedValueOnce(expectedUser);
  
      const user = await controller.create(createUserDto);
  
      expect(user).toBe(expectedUser);
    });
  });

  describe('otherwise', () => {
    it('should propagate service exceptions', async () => {
      const createUserDto = genCreateDto();
  
      const exception = new ConflictException('Error creating user');
      service.create.mockRejectedValueOnce(exception);
  
      let error: Error;
  
      try {
        await controller.create(createUserDto);
      } catch (err) {
        error = err;
      }
  
      expect(error).toBe(exception);
    });
  });
});

describe('findAll', () => {
  it('should return an array of users', async () => {
    const expectedUsers = [genUser(1), genUser(2)];

    service.findAll.mockResolvedValueOnce(expectedUsers);

    const users = await controller.findAll();

    expect(users).toBe(expectedUsers);
  });
});

describe('findOne', () => {
  describe('when user exists', () => {
    it('should return the user', async () => {
      const id = 1;
      const idDto: IdDto = { id };
      const expectedUser = genUser(id);

      service.findOne.mockResolvedValueOnce(expectedUser);

      const user = await controller.findOne(idDto);

      expect(user).toBe(expectedUser);
    });
  });

  describe('otherwise', () => {
    it('should propagate the exception', async () => {
      const id = 1;
      const idDto: IdDto = { id };

      const exception = new NotFoundException('User not found');
      service.findOne.mockRejectedValueOnce(exception);

      let error: Error;

      try {
        await controller.findOne(idDto);
      } catch (err) {
        error = err;
      }

      expect(error).toBe(exception);
    });
  });
});

describe('update', () => {
  describe('when user exists', () => {
    it('should update the user', async () => {
      const id = 1;
      const idDto: IdDto = { id };
      const existingUser = genUser(id);
      const updateUserDto = genUpdateDto();
      const expectedUser = {
        ...existingUser,
        ...updateUserDto,
      } as User;

      service.update.mockResolvedValueOnce(expectedUser);

      const user = await controller.update(idDto, updateUserDto);

      expect(user).toBe(expectedUser);
    });
  });

  describe('otherwise', () => {
    it('should propagate the exception', async () => {
      const id = 1;
      const idDto: IdDto = { id };
      const updateUserDto = genUpdateDto();

      const exception = new NotFoundException('User not found');
      service.update.mockRejectedValueOnce(exception);

      let error: Error;

      try {
        await controller.update(idDto, updateUserDto);
      } catch (err) {
        error = err;
      }

      expect(error).toBe(exception);
    });
  });
});

describe('remove', () => {
  describe('when user exists', () => {
    it('should remove the user', async () => {
      const id = 1;
      const idDto: IdDto = { id };
      const expectedUser = genUser(id);

      service.remove.mockResolvedValueOnce(expectedUser);

      const user = await controller.remove(idDto);

      expect(user).toBe(expectedUser);
    });
  });

  describe('otherwise', () => {
    it('should propagate the exception', async () => {
      const id = 1;
      const idDto: IdDto = { id };

      const exception = new NotFoundException('User not found');
      service.remove.mockRejectedValueOnce(exception);

      let error: Error;

      try {
        await controller.remove(idDto);
      } catch (err) {
        error = err;
      }

      expect(error).toBe(exception);
    });
  });
});
```

With this, we have finished the unit tests.

<mark style="color:green;">**Commit**</mark> - Implementing unit tests
