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

Testing for functional guards #640

Open
milo526 opened this issue Feb 13, 2024 · 2 comments
Open

Testing for functional guards #640

milo526 opened this issue Feb 13, 2024 · 2 comments

Comments

@milo526
Copy link

milo526 commented Feb 13, 2024

Description

There does not seem to be a proper way to test functional guard with spectator as yet.

It seems like they need to be tested using angular runInInjectionContext method.
https://netbasal.com/getting-to-know-the-runincontext-api-in-angular-f8996d7e00da

It would be nice to have first-class support for functional guards.

Proposed solution

Create a new API to test functional guards that can use spectators injector.

export const functionalGuard = (() => {
  const someService = inject(SomeService);

  return someService.canDoStuff();
}) satisfies CanActivateFn;

describe('functionalGuard', () => {
  let spectator: Spectator<theFunctionalGuard>;
  const createFunctionalGuard = createFunctionalGuardFactory({
    guard: theFunctionalGuard,
  });

  beforeEach(() => {
    spectator = createFunctionalGuard();
  });

  it('should test the guard', () => {
    spectator.runInInjectionContext((guard) => {
      // set some inputs for the guard, mock values

      expect(guard).toReturnWith(true);
    });
  });
});

Alternatives considered

More so a work-around than an alternative, creating an empty component and "stealing" its injector.

@Component({
  selector: 'app-empty-component',
  template: '',
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
})
class EmptyComponent {}

describe('functionalGuard', () => {
  let spectator: Spectator<EmptyComponent>;
  const createComponent = createComponentFactory(EmptyComponent);

  beforeEach(() => {
    spectator = createComponent();
  });

  it('should test the guard', () => {
    runInInjectionContext(spectator.inject(EnvironmentInjector), () => {
      // set some inputs for the guard, mock values

      expect(functionalGuard).toReturnWith(true);
    });
  });
});

Do you want to create a pull request?

I'd be open to trying to create a pull request if we can settle on a nice API.

@NetanelBasal
Copy link
Member

You are welcome to play with some ideas you have and see what's the best fit and open a PR.

@milo526
Copy link
Author

milo526 commented Feb 22, 2024

I've been working on this on-and-off and it turns out to be quite a bit more annoying than I anticipated.

The first step seems to be using TestBed.runInInjectionContext, this accepts a callback which will enable the use of the inject method.

Exposing it via the BaseSpectator (see snippet) allows us to use the injection context from any spectator object.

  public runInInjectionContext<T>(cb: () => T): T {
    return TestBed.runInInjectionContext(cb)
  }
spectator.runInInjectionContext(() => {
  const activatedRouteSnapshot = spectator.inject(ActivatedRouteSnapshot);
  const routerStateSnapshot = spectator.inject(RouterStateSnapshot);

  expect(functionalGuard(activatedRouteSnapshot, routerStateSnapshot)).toBe(true);
});

Some problems:

Functional guards most often use the CanActivateFn type

export declare type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;

As of yet the ActivatedRouteSnapshot can be created using the ActivedRouteStub of spectator fairly easily, I have not been able to find a proper way to generate a RouterStateSnapshot.

I'll continue working on this and see if I can create something useful

(Progress in fork - https://github.com/RiskChallenger/spectator)

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

2 participants