最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Testing Angular signal fails - Stack Overflow

programmeradmin3浏览0评论

footer component ts file:

import { AppSignalState } from './../../app-state/app-state.service';
import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
} from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-footer',
  imports: [CommonModule, RouterLink, RouterLinkActive],
  templateUrl: './footerponent.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: [
    `
      .nav .nav-item .active_section {
        color: #257bb1;
      }
    `,
  ],
})
export class FooterComponent {
  currentYear = new Date();
  // signals 
  appSignalState = inject(AppSignalState);
  serverBuildData = this.appSignalState.getStateServerBuildData();
  isUserAuthenticated = this.appSignalState.isUserLoggedIn();
  userMfaAuthRequired = this.appSignalState.userMfaAuthRequired();
  userResetPasswordRequired = this.appSignalState.userResetPasswordRequired();
  isContractAgreementRequired =
    this.appSignalState.isContractAgreementRequired();
  canShowLogin = computed(() => {
    return (
      !this.userMfaAuthRequired() &&
      !this.userResetPasswordRequired() &&
      !this.isContractAgreementRequired() &&
      !this.isUserAuthenticated()
    );
  });
}

footer component ts file:

import { AppSignalState } from './../../app-state/app-state.service';
import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
} from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-footer',
  imports: [CommonModule, RouterLink, RouterLinkActive],
  templateUrl: './footerponent.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: [
    `
      .nav .nav-item .active_section {
        color: #257bb1;
      }
    `,
  ],
})
export class FooterComponent {
  currentYear = new Date();
  // signals 
  appSignalState = inject(AppSignalState);
  serverBuildData = this.appSignalState.getStateServerBuildData();
  isUserAuthenticated = this.appSignalState.isUserLoggedIn();
  userMfaAuthRequired = this.appSignalState.userMfaAuthRequired();
  userResetPasswordRequired = this.appSignalState.userResetPasswordRequired();
  isContractAgreementRequired =
    this.appSignalState.isContractAgreementRequired();
  canShowLogin = computed(() => {
    return (
      !this.userMfaAuthRequired() &&
      !this.userResetPasswordRequired() &&
      !this.isContractAgreementRequired() &&
      !this.isUserAuthenticated()
    );
  });
}

test file is here:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footerponent';
import { AppSignalState } from '../../app-state/app-state.service';
import { signal, WritableSignal } from '@angular/core';
import { provideRouter } from '@angular/router';

describe('FooterComponent (Standalone)', () => {
  let component: FooterComponent;
  let fixture: ComponentFixture<FooterComponent>;
  let stateMock: jasmine.SpyObj<AppSignalState>;

  beforeEach(() => {
    const mockAppState = jasmine.createSpyObj('AppSignalState', [], {
      userMfaAuthRequired: signal(false) as WritableSignal<boolean>,
      userResetPasswordRequired: signal(false) as WritableSignal<boolean>,
      isContractAgreementRequired: signal(false) as WritableSignal<boolean>,
      isUserLoggedIn: signal(false) as WritableSignal<boolean>,
      getStateServerBuildData: signal({ version: '1.0.0' }) as WritableSignal<{
        version: string;
      }>,
    });

    TestBed.configureTestingModule({
      imports: [FooterComponent], // Import the standalone component
      providers: [
        provideRouter([]),
        {
          provide: AppSignalState,
          useValue: mockAppState,
        },
      ],
    })pileComponents();

    fixture = TestBed.createComponent(FooterComponent);
    component = fixtureponentInstance;

    stateMock = TestBed.inject(
      AppSignalState
    ) as jasmine.SpyObj<AppSignalState>;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should use the mocked signal', () => {
    expect(stateMock.userMfaAuthRequired()).toBe(false);
    expect(stateMock.userResetPasswordRequired()).toBe(false);
    expect(stateMock.isContractAgreementRequired()).toBe(false);
    expect(stateMock.isUserLoggedIn()).toBe(false);
  });

  it('should initialize currentYear with the current year', () => {
    expect(component.currentYear.getFullYear()).toBe(new Date().getFullYear());
  });
  it('should get serverBuildData from AppSignalState', () => {
    const mockServerBuildData = { version: '1.0.0' };
    expect(stateMock.getStateServerBuildData()).toEqual(mockServerBuildData);
  });
  it('should return true if all conditions are false', () => {
    // stateMock.userMfaAuthRequired.and.returnValue(signal(false));
    // stateMock.isContractAgreementRequired.and.returnValue(signal(false));
    // stateMock.isUserLoggedIn.and.returnValue(signal(false));
    // fixture.detectChanges();

    expect(component.canShowLogin()).toBe(true);
  });
});
service :

@Injectable({
  providedIn: 'root',
})
export class AppSignalState {
  private readonly stateSessionItem = 'state';
  private readonly state = signal<AppStateInterface>(appInitialState());
  private readonly encryptedState: boolean = false;
  private readonly JWT_TOKEN_WORD = 'Bearer_';
  private spinnerService = inject(NgxSpinnerService);
  mfaUtilityService = inject(MfaUtilityService);
  httpClient = inject(HttpClient);
 isUserLoggedIn(): Signal<boolean | unknown> {
    return computed(() => {
      return ...
  }
  userMfaAuthRequired(): Signal<boolean | unknown> {
    return computed(() => {
      return  ....
    });
  }
....


}

I am getting error in the canShowLogin test case : TypeError: this.userMfaAuthRequired is not a function also how to change the values of the signals and test canShowLogin with different parameters. the stateMock.userMfaAuthRequired.and.returnValue(signal(false)); not working and giving error.

thanks very much

Share edited Mar 7 at 19:56 Naren Murali 60.7k5 gold badges44 silver badges79 bronze badges asked Mar 7 at 17:57 Saeid KhodarahmiSaeid Khodarahmi 294 bronze badges 2
  • please share app-state.service – Naren Murali Commented Mar 7 at 18:06
  • I did include the servcie. – Saeid Khodarahmi Commented Mar 7 at 18:22
Add a comment  | 

1 Answer 1

Reset to default 1

I think the computed should not be inside a callback, since the computed itself is a callback.

@Injectable({
  providedIn: 'root',
})
export class AppSignalState {
  private readonly stateSessionItem = 'state';
  private readonly state = signal<any>(appInitialState());
  private readonly encryptedState: boolean = false;
  private readonly JWT_TOKEN_WORD = 'Bearer_';
  isUserLoggedIn: Signal<boolean | unknown> = computed(() => {
    return true;
  });
  userMfaAuthRequired: Signal<boolean | unknown> = computed(() => {
    return true;
  });
  getStateServerBuildData: Signal<boolean | unknown> = computed(() => {
    return true;
  });
  userResetPasswordRequired: Signal<boolean | unknown> = computed(() => {
    return true;
  });
  isContractAgreementRequired: Signal<boolean | unknown> = computed(() => {
    return true;
  });
}

Now we can directly access the computed, using the method below.

@Component({
  selector: 'my-app',
  templateUrl: './appponent.html',
  imports: [CommonModule, RouterLink, RouterLinkActive],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: [
    `
      .nav .nav-item .active_section {
        color: #257bb1;
      }
    `,
  ],
})
export class FooterComponent {
  currentYear = new Date();
  // signals
  appSignalState = inject(AppSignalState);
  serverBuildData = this.appSignalState.getStateServerBuildData;
  isUserAuthenticated = this.appSignalState.isUserLoggedIn;
  userMfaAuthRequired = this.appSignalState.userMfaAuthRequired;
  userResetPasswordRequired = this.appSignalState.userResetPasswordRequired;
  isContractAgreementRequired = this.appSignalState.isContractAgreementRequired;
  canShowLogin = computed(() => {
    return (
      !this.userMfaAuthRequired() &&
      !this.userResetPasswordRequired() &&
      !this.isContractAgreementRequired() &&
      !this.isUserAuthenticated()
    );
  });
}

From the testing perspective, it is better not to use a spy here, since you are targetting reactivity, toggling the flags and expecting the computed to react to the changes.

So define a class, this class will be used to trigger the reactivity of the component.

class MockAppState {
  userMfaAuthRequired = signal(false) as WritableSignal<boolean>;
  userResetPasswordRequired = signal(false) as WritableSignal<boolean>;
  isContractAgreementRequired = signal(false) as WritableSignal<boolean>;
  isUserLoggedIn = signal(false) as WritableSignal<boolean>;
  getStateServerBuildData = signal({ version: '1.0.0' }) as WritableSignal<{
    version: string;
  }>;
}

We then provide this class over the original service.

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [FooterComponent], // Import the standalone component
    providers: [
      provideRouter([]),
      {
        provide: AppSignalState,
        useClass: MockAppState,
      },
    ],
  })pileComponents();

  fixture = TestBed.createComponent(FooterComponent);
  component = fixtureponentInstance;

  stateMock = TestBed.inject(AppSignalState) as unknown as MockAppState;
});

Notice I am doing as unknown as MockAppState, this is because we are using a signal to a property which should be a computed. Since the code was not shared for the computed, I am doing it this way.

Now for the test cases, we simply use .set() method to set the values. Then trigger fixture.detectChanges() for the computed to have the latest value, finally check the result.

it('should return true if all conditions are false', () => {
  stateMock.userMfaAuthRequired.set(false);
  stateMock.isContractAgreementRequired.set(false);
  stateMock.isUserLoggedIn.set(false);
  fixture.detectChanges();

  expect(component.canShowLogin()).toBe(true);
});

Full Code:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { signal, WritableSignal } from '@angular/core';
import { provideRouter } from '@angular/router';
import { AppSignalState, FooterComponent } from './appponent';

    class MockAppState {
      userMfaAuthRequired = signal(false) as WritableSignal<boolean>;
      userResetPasswordRequired = signal(false) as WritableSignal<boolean>;
      isContractAgreementRequired = signal(false) as WritableSignal<boolean>;
      isUserLoggedIn = signal(false) as WritableSignal<boolean>;
      getStateServerBuildData = signal({ version: '1.0.0' }) as WritableSignal<{
        version: string;
      }>;
    }

describe('FooterComponent (Standalone)', () => {
  let component: FooterComponent;
  let fixture: ComponentFixture<FooterComponent>;
  let stateMock: MockAppState;

    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [FooterComponent], // Import the standalone component
        providers: [
          provideRouter([]),
          {
            provide: AppSignalState,
            useClass: MockAppState,
          },
        ],
      })pileComponents();

      fixture = TestBed.createComponent(FooterComponent);
      component = fixtureponentInstance;

      stateMock = TestBed.inject(AppSignalState) as unknown as MockAppState;
    });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should use the mocked signal', () => {
    expect(stateMock.userMfaAuthRequired()).toBe(false);
    expect(stateMock.userResetPasswordRequired()).toBe(false);
    expect(stateMock.isContractAgreementRequired()).toBe(false);
    expect(stateMock.isUserLoggedIn()).toBe(false);
  });

  it('should initialize currentYear with the current year', () => {
    expect(component.currentYear.getFullYear()).toBe(new Date().getFullYear());
  });
  it('should get serverBuildData from AppSignalState', () => {
    const mockServerBuildData: any = { version: '1.0.0' };
    expect(stateMock.getStateServerBuildData()).toEqual(mockServerBuildData);
  });
    it('should return true if all conditions are false', () => {
      stateMock.userMfaAuthRequired.set(false);
      stateMock.isContractAgreementRequired.set(false);
      stateMock.isUserLoggedIn.set(false);
      fixture.detectChanges();

      expect(component.canShowLogin()).toBe(true);
    });

  it('canShowLogin should return false if any of the conditions are true', () => {
    stateMock.userMfaAuthRequired.set(true);
    stateMock.isContractAgreementRequired.set(true);
    stateMock.isUserLoggedIn.set(true);
    stateMock.userResetPasswordRequired = signal(false) as any;
    fixture.detectChanges();
    expect(component.canShowLogin()).toBe(false);
  });
});

Stackblitz Demo

发布评论

评论列表(0)

  1. 暂无评论