felanios / murlock

MurLock: A distributed locking solution for NestJS, providing a decorator for critical sections with Redis-based synchronization. Ideal for microservices and scalable applications.
https://www.npmjs.com/package/murlock
MIT License
55 stars 1 forks source link

Usage in jest integration tests #39

Open flodaniel opened 4 months ago

flodaniel commented 4 months ago

Due to how the AsyncStorageManager.runWithNewContext is called through the AsyncStorageInterceptor, I am struggling how to get my jest integration tests to work once I use a decorator. There is no problem in e2e tests as the AsyncStorageInterceptor is called.

What is the recommended way for using this during tests?

felanios commented 4 months ago

Hi @flodaniel, maybe this kind of implementation can be used.

import { ExecutionContext, CallHandler } from '@nestjs/common';
import { of } from 'rxjs';
import { AsyncStorageManager } from '../lib/als/als-manager';
import { AsyncStorageInterceptor } from '../lib/interceptors';

describe('AsyncStorageInterceptor', () => {
  let asyncStorageManager: AsyncStorageManager<string>;
  let interceptor: AsyncStorageInterceptor;

  beforeEach(() => {
    asyncStorageManager = new AsyncStorageManager<string>();
    asyncStorageManager.runWithNewContext = jest.fn().mockImplementation((fn) => fn());
    asyncStorageManager.get = jest.fn();
    asyncStorageManager.set = jest.fn();

    interceptor = new AsyncStorageInterceptor(asyncStorageManager);

    jest.clearAllMocks();
  });

  it('AsyncStorageInterceptor should manage async storage context', async () => {
    const mockExecutionContext = {} as ExecutionContext;
    const mockCallHandler: CallHandler = {
      handle: () => of('test')
    };

    await interceptor.intercept(mockExecutionContext, mockCallHandler).toPromise();

    expect(asyncStorageManager.runWithNewContext).toHaveBeenCalled();
  });
});
brandart commented 4 months ago

@felanios thanks for the snippet, however the AsyncStorageInterceptor and the AsyncStorageManager is not exported by the package

felanios commented 4 months ago

@brandart, for just test purpose you can import like that maybe;

import { AsyncStorageInterceptor } from 'murlock/dist/interceptors';
import { AsyncStorageManager } from 'murlock/dist/als/als-manager';
brandart commented 4 months ago

@felanios thanks :)

I still haven't managed to test a method that uses the @Murlock() decorator.

your snippet succeeds but I don't see the part where I should call the custom method that uses the Decorator.

I tried like this:

it('should test my custom method and should not fail', async () => {
    const mockExecutionContext = {} as ExecutionContext;
    const mockCallHandler: CallHandler = {
      handle: () => of('test')
    };

    await interceptor.intercept(mockExecutionContext, mockCallHandler).toPromise();

    expect(asyncStorageManager.runWithNewContext).toHaveBeenCalled();

    const result = service.testCustomMethodWithMurLockDecorator();

    expect(result).toBeDefined();
  });

however I still receive the error: No active store found

raphaelsalomao-ext-nomad commented 1 week ago

I've managed to test functions with @Murlock() using await module.createNestApplication().init();

Something like this:

beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        MurLockModule.forRootAsync({
          imports: [ConfigModule],
          useFactory: async (configService: ConfigService) => ({
            redisOptions: configService.redis,
            wait: 200,
            maxAttempts: 1,
            logLevel: 'log',
            ignoreUnlockFail: false,
          }),
          inject: [ConfigService],
        }),
      ],
    })
      .compile();

    app = module.createNestApplication();

    await app.init();
  });

And then:

it('should test my custom method and should not fail', async () => {
    const result = service.testCustomMethodWithMurLockDecorator();

    expect(result).toBeDefined();
  });