2

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?

Shachar Har-Shuv
  • 666
  • 6
  • 24

2 Answers2

1

You could use it as a factory in TestBed provider, and assert against the provided value.


// this is the function we will be testing
function getPlatformName() {
  const platformId = inject(PLATFORM_ID); // uses inject()
  return isPlatformBrowser(platformId) ? "BROWSER" : "SERVER";
}

describe("getPlatformName", () => {
  it("supports browser", () => {
    TestBed.configureTestingModule({
      // provide an arbitrary token, with our fn as a factory
      providers: [{ provide: "platformName", useFactory: getPlatformName }],
    });

    expect(TestBed.inject("platformName" as any)).toEqual("BROWSER");
  });

  it("supports server", () => {
    TestBed.configureTestingModule({
      providers: [
        { provide: PLATFORM_ID, useValue: "server" },
        { provide: "platformName", useFactory: getPlatformName },
      ],
    });

    expect(TestBed.inject("platformName" as any)).toEqual("SERVER");
  });
});

If you're verify side effects and not a return value, you still do the same (use as a factory, then TestBed.inject()). After that you can assert on mocks or whatever side effects it was supposed to perform.

amakhrov
  • 3,820
  • 1
  • 13
  • 12
1

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.

Allan Juan
  • 2,048
  • 1
  • 18
  • 42
Shachar Har-Shuv
  • 666
  • 6
  • 24