#Use of toSignal best practices & testing

4 messages · Page 1 of 1 (latest)

fluid estuary
#

Hello !

I'm refactoring my code to fully embrace signals and try to follow the best practices.

But I would like to be sure it's really the best practice (pretty sure it is ... to be honest the code seems so much nicer now).
And I'm also struggling testing my component and I'm not sure the solution I've found is the correct one (or at least I would be glad to have some other proposal).

Here is an example with a dummy component.

I went from class component with observable to signal based component

// OLD
@Component({
  ...
})
export class StuffComponent implements OnInit {
  private readonly _stuffService: StuffService = inject(StuffService);

  public stuffList: Stuff[] = [];

  public ngOnInit(): void {
    this._stuffService.getStuffList().subscribe((stuffList) => {
      this.stuffList = stuffList;
    })
  }
}

to

@Component({
  ...
})
export class StuffComponent {
  private readonly _stuffService: StuffService = inject(StuffService);

  public readonly  stuffList: Signal<Stuff[]> = toSignal(this._stuffService.getStuffList());
}
#

And now I would like to test that my component fetch data from my service and set data

describe('StuffComponent', () => {
  ...

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [...]
    }).compileComponents();

    fixture = TestBed.createComponent(StuffComponent);
    component = fixture.componentInstance;
  });

  it('should set stuff', () => {
    const stuffMock: Stuff[] = [new Stuff()];
    spyOn(TestBed.inject(StuffService), 'getStuffList').and.returnValue(of(stuffMock));

    fixture.detectChanges();

    expect(component.stuffList).toEqual(stuffMock);
  });
});

But this doesn't work because with the signal based approach my signal is set in the constructor, even before my test is triggered ...

The solution I've found is to remove the component initialization from the beforeEach block and set it in my test like this.

describe('StuffComponent', () => {
  ...

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [...]
    }).compileComponents();
  });

  function initComponent(): void {
    fixture = TestBed.createComponent(StuffComponent);
    component = fixture.componentInstance;
  }

  it('should set stuff', () => {
    const stuffMock: Stuff[] = [new Stuff()];
    spyOn(TestBed.inject(StuffService), 'getStuffList').and.returnValue(of(stuffMock));

    initComponent();

    expect(component.stuffList).toEqual(stuffMock);
  });

Am I doing right ? Any suggestion ?

Thank you 🙂

neat pivot
#

Yes, what you're doing is right. If all the tests use the same stuff data, you could also move the spyOn inside the beforeEach of course.
I generally prefer to create mocks and provide them rather than spying on the real service. It makes sure the test doesn't call actual methods of the service, and it allows avoiding to have to setup all the dependencies of the service (the http client typically) in the test.

fluid estuary
#

Okay great thank you !