How To Test Functional Guards in Angular 15
Tags: angular, guards, testing, javascript
Published: 2023-04-16 15:49:55

Since Angular 14, we can convert our class guards to functional and also combine them with `inject` making it so easy to write guards Angular applications.

When we move from class to functional, the constructor disappears, and the dependencies come from inject, but how much is the testing impacted?

## Functional Guard

In a prior blog post about functional guards, we explored the shift [from class-based to functional guards](https://www.danywalls.com/how-to-use-functional-router-guards-in-angular) in Angular applications.

We have a functional guard, \`domainGuard\`; it uses the inject function to get two dependencies: the router and the Domain Service.

```typescript
import {inject} from '@angular/core';
import {Router} from '@angular/router';
import {tap} from 'rxjs';
import {DomainService} from '../domain.service';

export const domainGuard = () => {
    const router = inject(Router);
    const service = inject(DomainService)
    return service.isAvailable().pipe(
    tap((value) => {
      return !value ? router.navigate(['/no-available']) : true
    }
  ))
}
```

We'll be utilizing TestBed along with a custom function to ensure thorough test coverage. Let's dive in!

## Using Testbed

Using Testbed simplifies the process of configuring tests, but remember that we use the inject function to supply our dependencies, and the inject function can be found within the function body only.

But In Angular 15, the runInInjectionContext feature enables us to supply our dependencies previously provided through the inject function.

> Read more [https://angular.io/api/core/testing/TestBed#runInInjectionContext](https://angular.io/api/core/testing/TestBed#runInInjectionContext)

Instead of using BeforeEach, I've developed a custom setup function that generates an instance of our guard with TestBed.

The domainGuard relies on two dependencies: router and domainService.

* Rather than importing the RouterTestingModule, I'll mock the Router using \`createSpyObject\`.
    
* The setup function takes a domainServiceMock parameter, allowing me to tailor the domainService behavior for each test.
    
* Retrieve the domainGuard instance by utilizing \`TestBed.runInInjectionContext\`.
    

The final code looks like this:

```typescript
const mockRouter = jasmine.createSpyObj<Router>(['navigate'])
  mockRouter.navigate.and.returnValue(lastValueFrom(of(true)))

  const setup = (domainServiceMock: unknown) => {
    TestBed.configureTestingModule({
      providers: [
        domainGuard,
        { provide: DomainService, useValue: domainServiceMock},
        { provide: Router, useValue: mockRouter}
      ]
    });
    
    return TestBed.runInInjectionContext(domainGuard);
  }
```

## Write Tests

We'll be writing two tests, and for each, we'll invoke the setup function to obtain the guard instance. This allows us to tailor the domainService according to our test requirements.

The final code looks like this:

```typescript
  it('should allow to continue', () => {

    const mockDomainService : unknown = {
      isAvailable: () => of(true)
    }
    const guard = setup(mockDomainService);
    guard.subscribe((p: unknown) => {
      expect(p).toBe(true)
    })
  })

  it('should redirect to /no-available path', () => {

    const mockDomainService: unknown = {
      isAvailable: () => of(false)
    }

    const guard = setup(mockDomainService);
    guard.subscribe((p: unknown) => {
      expect(p).toBe(false)
      expect(mockRouter.navigate).toHaveBeenCalled()
    })
  })
```

Done! We are testing our functional guard easily!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681656840572/debc1641-0f6a-4124-b458-7984a29a1c47.png align="center")

## Final

Transforming our class guard into a functional format is remarkably straightforward, and the necessary modifications to our tests are minimal. The key to achieving this lies in the clever utilization of TestBed.runInInjectionContext in conjunction with Testbed, allowing for a seamless transition and enhanced functionality.

If you have questions, concerns, or thoughts, please comment below. For a deeper technical dive, check out the source code on GitHub.

* [Github](https://github.com/danywalls/functional-guards-angular)


Photo by Julia Koblitz on Unsplash