Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock an angular class with signals?

I've an angular web app. I'm trying to test some services, that have other services as depenency. I need to mock those others services to test my service. I'm using Jest+auto-jest-spies to mock my classes, but I'm open to other suggestions.

Here is an example of class that I'm trying to mock(a store made with signalstory) :

import { computed, Injectable } from '@angular/core';
import { ImmutableStore, useDevtools } from 'signalstory';
import { AccountStateModel } from './account-state.model';

@Injectable({ providedIn: 'root' })
export class AccountStore extends ImmutableStore<AccountStateModel> {
  constructor() {
    super({
      plugins: [useDevtools()],
      initialState: {
        isInitialized: false,
        email: null,
        accessToken: null,
        name: null,
        tokenExpiration: null,
        userId: null,
        permissions: null,
      },
    });
  }

  //Queries
  public get isLoggedIn() {
    return computed(() => !!this.state().userId);
  }
  public get userInfo() {
    return this.state;
  }

  // Commands
  //...
}

I'm trying to mock it like this:

describe('PermissionGuard', () => {
  let storeMock!: Spy<AccountStore>;
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: AccountStore, useValue: createSpyFromClass(AccountStore, { gettersToSpyOn: ['isLoggedIn'] }) },
        PermissionGuard,
      ],
    }).compileComponents();
    storeMock = TestBed.inject<any>(AccountStore);
  });

it('should test the user access', async () => {
  //Arrange
  storeMock.isLoggedIn.mockReturnValue(false);

  //Act
  //...

  //Assert
  //...

  });
});

But the isLoggedIn is not recognized as a getter, I guess because it's a getter of a "function"(signal).

So what can I do to mock this class? I also want to make sure a signal has been accessed.

like image 909
J4N Avatar asked Oct 27 '25 13:10

J4N


1 Answers

Essentially, the problem is that the getter function returns a signal, which is by itself a function. To keep all the characteristics of the signal intact, you could do something like this instead:


describe('PermissionGuard', () => {
  let storeMock: Spy<AccountStore>;
  let permissionGuard: PermissionGuard;
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        {
          provide: AccountStore,
          useValue: createSpyFromClass(AccountStore, {
            methodsToSpyOn: ['isLoggedIn'],
          }),
        },
        PermissionGuard,
      ],
    }).compileComponents();
    storeMock = TestBed.inject(AccountStore) as Spy<AccountStore>;
    permissionGuard = TestBed.inject(PermissionGuard);
  });

  it('should test the user access', () => {
    //Arrange
    storeMock.isLoggedIn.mockImplementation(signal(true));

    //Act
    const result = permissionGuard.isValid();

    //Assert
    expect(result).toBe(true);
    expect(storeMock.isLoggedIn).toHaveBeenCalledTimes(1);
    expect(storeMock.isLoggedIn).toHaveBeenCalledWith();
  });
});

Assuming here that PermissionGuard is defined like


@Injectable({ providedIn: 'root' })
export class PermissionGuard {
  constructor(private readonly account: AccountStore) {}

  isValid() {
    return this.account.isLoggedIn();
  }
}

like image 112
zuriscript Avatar answered Oct 29 '25 07:10

zuriscript



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!