Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to mock dataSource #7

Open
MBach opened this issue Jan 6, 2025 · 3 comments
Open

How to mock dataSource #7

MBach opened this issue Jan 6, 2025 · 3 comments

Comments

@MBach
Copy link

MBach commented Jan 6, 2025

Hello,

In your documentation, you're assuming that we can access the datasource directly. However, I have a big service like:

@Injectable()
export class ChuteService extends BaseService {
    constructor(
        private dataSource: DataSource,
        
        @InjectRepository(SomeRepo)
        private someRepo: Repository<SomeRepo>,
        
    ) {
        super()
    }
}

Inside, for some methods I'm using transactions like:

public async updateSomeAttribute(
        attrId : string
    ): Promise<SomeOtherEntity> {
        const queryRunner = this.dataSource.createQueryRunner()
        await queryRunner.connect()
        await queryRunner.startTransaction()
        try {
            let obj: SomeOtherEntity= await queryRunner.manager
                .createQueryBuilder(SomeOtherEntity, "s")
                .innerJoinAndSelect("s.anotherOne", "i")
                .where("s.id = :attrId", { attrId })
                .getOneOrFail()

          
            ent = await queryRunner.manager.save<SomeOtherEntity>(entity)
            await queryRunner.commitTransaction()
            return ent 
        } catch (error) {
            await queryRunner.rollbackTransaction()
            throw new BadRequestException(error, "Transaction failed")
        } finally {
            await queryRunner.release()
        }
    }

How can I use your package in my test? I'd like to reflect that some methods are called in my unit test, and to be able to use the dataSource from my service, but it's injected in my real service

describe("Update some attr", () => {
        it("should update some attr and return a Entity or null", async () => {
            
            const typeorm = new MockTypeORM()
            typeorm.onMock(SomeEntity).toReturn(entity, "getOneOrFail")

            const queryRunner = dataSource.createQueryRunner()
            queryRunner.manager.createQueryBuilder()

            // Call the Service
            const result = await myService.updateSomeAttribute(attrId)

            expect(queryRunner.connect).toHaveBeenCalled()
            expect(queryRunner.startTransaction).toHaveBeenCalled()
            expect(queryRunner.commitTransaction).toHaveBeenCalled()
            expect(queryRunner.release).toHaveBeenCalled()
        })
    })
@jazimabbas
Copy link
Owner

I haven't tested on Nestjs, but I'll take a look into your example this weekend and then will let you know. Sorry I am overloaded with projects right now. But I'll definitely take a look this weekend.

@Monstermash28425R1
Copy link

hello there @MBach, i've been migrating my nestjs services from repositories to dataSource usage, and this is the way i test them:

import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { MockTypeORM } from 'mock-typeorm';
import { send } from 'process';
import { DataSource } from 'typeorm';

import { dataSource } from '../../../../test/mocks/data-source';
import { PaginationMetadata } from '../../../common/dto/pagination-metadata.dto';
import { ApplicationGuard } from '../../../common/guards/application.guard';
import { AuthService } from '../../../modules/auth/services/auth.service';
import { DeviceEntity } from '../../../modules/device/device.entity';
import { DeviceGateway } from '../../../modules/device/device.gateway';
import { CreateDeviceDto } from '../../../modules/device/dto';
import { DeviceService } from '../../../modules/device/services/device.service';
import { ApiConfigService } from '../../../shared/services/api-config.service';
import { DevicesController } from './devices.controller';

describe('DevicesController', () => {
  let controller: DevicesController;
  let typeorm: MockTypeORM;
  beforeEach(async () => {
    typeorm = new MockTypeORM();
    const module: TestingModule = await Test.createTestingModule({
      controllers: [DevicesController],
      providers: [
        DeviceService,
        {
          provide: 'default_IORedisModuleConnectionToken',
          useValue: {},
        },
        {
          provide: DataSource,
          useValue: dataSource,
        },
        {
          provide: ApiConfigService,
          useValue: {
            authConfig: {
              privateKey: 'rsa-private-key',
            },
          },
        },
        {
          provide: AuthService,
          useValue: {
            createAccessToken: () => 'generated-token',
          },
        },
        {
          provide: DeviceGateway,
          useValue: {
            sendCommandTo: () => {},
          },
        },
      ],
    })
      .overrideGuard(ApplicationGuard)
      .useValue({
        canActivate() {
          return true;
        },
      })
      .compile();

    controller = module.get<DevicesController>(DevicesController);
  });

  afterEach(() => {
    typeorm.restore();
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});

and this is my dataSource for mocking:

import { config } from 'dotenv';
import { DataSource } from 'typeorm';

import { UserSubscriber } from '../../src/entity-subscribers/user.subscriber';
import { SnakeNamingStrategy } from '../../src/snake-naming.strategy';

config();

export const dataSource = new DataSource({
  type: 'mysql',
  database: 'mock',
  namingStrategy: new SnakeNamingStrategy(),
  subscribers: [UserSubscriber],
  entities: ['src/**/*.entity.{ts,js}'],
  migrations: ['database/mysql/migrations/*.{ts,js}'],
});

note that i'm moking all the 3rd party dependencies and the guards so i just need to focus in my actual code to be tested, i dont know if this is your solution but it's the implementation i found searching around the internet and doing many attempts :)

@Monstermash28425R1
Copy link

@MBach i forgot to put an actual example of the test so here it is:

 describe('findOne', () => {
    it('Must find a building with given ID', async () => {
      const building = [{ id: '1', name: 'Building 1' }];
      typeorm.onMock(BuildingEntity).toReturn(building, 'findOne');

      const result = await service.findOne('1');

      expect(result).toStrictEqual(building);
      expect(
        (dataSource.manager.findOne as SinonSpy)
          .getCall(0)
          .calledWith(BuildingEntity, {
            where: { id: '1' },
            relations: ['organization'],
          }),
      ).toBeTruthy();
    });

    it('Must throw a NotFoundException error if not building is found', async () => {
      typeorm.onMock(BuildingEntity).toReturn(undefined, 'findOne');

      await expect(service.findOne('1')).rejects.toThrow(
        new NotFoundException('Building not found.'),
      );
    });
  });

however, i haven't implement a test for transactions nor query builders yet so i'm not pretty sure what's your issue right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants