Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test a function that uses the new Angular 14's "inject" function?

I've created a function that utilizes that new Angular 'inject' function. Since the inject function can be used only when initializing a class (or factory) that is part of the dependency injection tree, this function is meant to be used in the constructor of a component / service.

I want to unit test this function with mock dependencies. The problem is I can't just call it in unit tests, because it will be called in an incorrect context. I can create a component / service just for the unit test purposes but it feels like too much boilerplate to test a simple function.

Is there a recommended way of doing this?

like image 203
Shachar Har-Shuv Avatar asked Sep 03 '25 16:09

Shachar Har-Shuv


2 Answers

I know it's been a while, but I finally thought of a solution that is elegant enough to be the standard way of doing it.

Let's assume the function you want to test is myInjectionFunction:

// This is a utility function you can put in a shared place and use it whenever you want
function useInjectionFunction<GArgs extends any[], GReturn>(injectionFunction: (...args: GArgs) => GReturn, injector: Injector) {
  const token = new InjectionToken<GReturn>(injectionFunction.name);
  return (...args: GArgs) => Injector.create({
    parent: injector,
    providers: [
      {
        provide: token,
        useFactory: () => {
          return injectionFunction(...args);
        },
      },
    ],
  }).get(token)
}

describe('My Test suite', () => {
  let myFunction: typeof myInjectionFunction;

  beforeEach(() => {
    TestBed.configureTestingModule({
      // configure your providers here
    });

    myFunction = useInjectionFunction(hasFeatureFlag, TestBed.get(Injector));
  });

  it('should work', () => {
    // call "myFunction" regularly to test it
  });
});

Leaving it here if anyone wants to use it.

like image 75
Shachar Har-Shuv Avatar answered Sep 05 '25 04:09

Shachar Har-Shuv


You can use runInInjectionContext for this, take for example testing an Angular HTTP interceptor that prohibits access based on a configuration service. You first set up your test bed as you would do with components:

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      {
        provide: ConfigurationService,
        useFactory: () => createSpyObj(ConfigurationService, ['getEnvironmentVariable'])
      },
    ]
  })
});

Then you can write your tests and execute your function that requires injection inside the runInInjectionContext callback as such:

it('should prohibit http', () => {
  const config: any = TestBed.inject(ConfigurationService);
  config.allowHttp.and.returnValue(false)
  const fakeHttpRequest = {url: 'http://www.test.be'} as HttpRequest<unknown>;
  TestBed.runInInjectionContext(() => {
    authHttpInterceptor(fakeHttpRequest, null).subscribe((response) => {
      expect(response.statusCode).toBe(505)
    })
  })
})
like image 32
Pieter De Bie Avatar answered Sep 05 '25 04:09

Pieter De Bie