Let's then finish the unit tests section by creating tests for the controller.
We can go to the file users.controller.spec to begin, and also attempt to run this test file.
yarntest:watch--users.controller
But the same error will occur, as the original service is included in the module. Therefore, it will attempt to find the Repository inside the module and will fail. We don't want the actual service here, much less the actual Repository. Only the unit that we are testing should be the focus of the test, everything else should be mocked. Let's then fix this.
Back in the testing.util file, we should create a type for a MockService and a function to instantiate one. Currently, there's nothing that uniquely identifies a service apart from it being a class, so instead let's create a type for a MockClass and the function createMockInstance().
The MockClass type will be a bit similar to MockRepository, but with a difference. Here, we should use a type parameter to indicate what will be mocked. To enforce that it should be a class, we can make the type parameter extend from the Type interface, which represents a generic constructor. There is just something we should be aware of: this type parameter will not accept the class itself, but the typeof the class. Due to this, we should give to MockProxy not the type argument itself, but a type representing an instance of this type by using InstanceType. We then get the following:
And for the function to generate the instance, we'll use the same generic representing a class. We'll also have an actual regular parameter of this type, and call the mock() function passing the typeof this Class.
Create a variable for the service, using typeof as was mentioned
let service:MockClass<typeof UsersService>;
Call the function to instantiate the desired class
useValue:createMockInstance(UsersService),
Obtain a reference to it from the module
service =module.get<MockClass<typeof UsersService>>(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 UsersService tests. The only thing that may be a bit different is that some route handlers expect an idDto, so we should also have one there.
describe('create', () => {describe('when no error occurs', () => {it('should create a user',async () => {constid=1;constcreateUserDto=genCreateDto();constexpectedUser=genUser(id, createUserDto);service.create.mockResolvedValueOnce(expectedUser);constuser=awaitcontroller.create(createUserDto);expect(user).toBe(expectedUser); }); });describe('otherwise', () => {it('should propagate service exceptions',async () => {constcreateUserDto=genCreateDto();constexception=newConflictException('Error creating user');service.create.mockRejectedValueOnce(exception);let error:Error;try {awaitcontroller.create(createUserDto); } catch (err) { error = err; }expect(error).toBe(exception); }); });});describe('findAll', () => {it('should return an array of users',async () => {constexpectedUsers= [genUser(1),genUser(2)];service.findAll.mockResolvedValueOnce(expectedUsers);constusers=awaitcontroller.findAll();expect(users).toBe(expectedUsers); });});describe('findOne', () => {describe('when user exists', () => {it('should return the user',async () => {constid=1;constidDto:IdDto= { id };constexpectedUser=genUser(id);service.findOne.mockResolvedValueOnce(expectedUser);constuser=awaitcontroller.findOne(idDto);expect(user).toBe(expectedUser); }); });describe('otherwise', () => {it('should propagate the exception',async () => {constid=1;constidDto:IdDto= { id };constexception=newNotFoundException('User not found');service.findOne.mockRejectedValueOnce(exception);let error:Error;try {awaitcontroller.findOne(idDto); } catch (err) { error = err; }expect(error).toBe(exception); }); });});describe('update', () => {describe('when user exists', () => {it('should update the user',async () => {constid=1;constidDto:IdDto= { id };constexistingUser=genUser(id);constupdateUserDto=genUpdateDto();constexpectedUser= {...existingUser,...updateUserDto, } asUser;service.update.mockResolvedValueOnce(expectedUser);constuser=awaitcontroller.update(idDto, updateUserDto);expect(user).toBe(expectedUser); }); });describe('otherwise', () => {it('should propagate the exception',async () => {constid=1;constidDto:IdDto= { id };constupdateUserDto=genUpdateDto();constexception=newNotFoundException('User not found');service.update.mockRejectedValueOnce(exception);let error:Error;try {awaitcontroller.update(idDto, updateUserDto); } catch (err) { error = err; }expect(error).toBe(exception); }); });});describe('remove', () => {describe('when user exists', () => {it('should remove the user',async () => {constid=1;constidDto:IdDto= { id };constexpectedUser=genUser(id);service.remove.mockResolvedValueOnce(expectedUser);constuser=awaitcontroller.remove(idDto);expect(user).toBe(expectedUser); }); });describe('otherwise', () => {it('should propagate the exception',async () => {constid=1;constidDto:IdDto= { id };constexception=newNotFoundException('User not found');service.remove.mockRejectedValueOnce(exception);let error:Error;try {awaitcontroller.remove(idDto); } catch (err) { error = err; }expect(error).toBe(exception); }); });});