angularrxjsjasminekarma-jasmine

Can not seem to mock behavior subject in Angular unit test


Using Angular 16 and Jasmine/Karma.

I have this subscription to a behavior subject called tableList$


    ngOnInit() {                                                   
      const { pageNumber } = this.subscribersService.getMiscValues();
      this.subscribersService.initPage(pageNumber);
>>    this.subscribersService.tableList$.subscribe((tableList) => {
        //  this.tableList = this.deleteSubscribersService.setDeleteCheckboxStates(tableList);                    
      });                                                                                
    } 

I am trying to mock the behavior subject, but I am getting this.subscribersService.tableList$.subscribe is not a function.

Any ideas what I am doing wrong, please?

Note, I also have tried subscribersServiceMock.tableList$.and.returnValue({ subscribe: () => tableListMock$.subscribe }); to no success

fdescribe('ManageSubscribersComponent', () => {
  let component: ManageSubscribersComponent;
  let fixture: ComponentFixture<ManageSubscribersComponent>;
  let miscMockValues;
  let tableMockData;
  let tableListMock$;
  let dialogServiceMock
  let dialogSubjectMock;
  let subscribersServiceMock;
  let deleteSubscribersServiceMock;

  const displayedColumns = [
    'id',
    'name',
    'description',
    'createdBy',
    'createdOn',
    'updatedBy',
    'updatedOn',
  ];

  beforeEach(() => {
    tableListMock$ = new Subject();
    dialogSubjectMock = new Subject();

    dialogServiceMock = jasmine.createSpyObj([
      'open',
      'afterClosed',
    ]);

    deleteSubscribersServiceMock = jasmine.createSpyObj([
      'deleteSubscribers',
    ]);


    subscribersServiceMock = jasmine.createSpyObj([
      'resetMiscValues',
      'getMiscValues',
      'initPage',
      'tableList$',
    ]);

    miscMockValues = {
      pageNumber: 1,
      pageSize: 25,
      sort: '',
      active: true,
      testMode: false,
      searchTerms: '',
    };


    subscribersServiceMock.getMiscValues.and.returnValue(miscMockValues);
    subscribersServiceMock.tableList$.and.returnValue(tableListMock$);
    dialogServiceMock.afterClosed.and.returnValue(dialogSubjectMock);

    TestBed.configureTestingModule({
      declarations: [ManageSubscribersComponent],
      imports: [
        HttpClientTestingModule,
        MatFormFieldModule,
        MatIconModule,
        MatSliderModule,
        MatTableModule,
        MatFormFieldModule,
        MatInputModule,
        BrowserAnimationsModule,
        MatPaginatorModule,
        MatDialogModule,
      ],
      schemas: [
        CUSTOM_ELEMENTS_SCHEMA,
        NO_ERRORS_SCHEMA,
      ],
      providers: [
        {
          provide: SubscribersService,
          useValue: subscribersServiceMock,
        },
        {
          provide: DeleteSubscribersService,
          useValue: deleteSubscribersServiceMock,
        },
      ],
    });

    fixture = TestBed.createComponent(ManageSubscribersComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });


  fit('should init', fakeAsync(() => {
    fixture.whenStable().then(() => {
      expect(component.displayedColumns).toEqual(displayedColumns);
      expect(subscribersServiceMock.getMiscValues).toHaveBeenCalled();
    })
  }));

Solution

  • As stated in comments the issue lies on the test setup

    When setting up the SubscribersService mock by doing

    subscribersServiceMock = jasmine.createSpyObj([
          'resetMiscValues',
          'getMiscValues',
          'initPage',
          'tableList$',
        ]);
    

    Those names in the array will be methods to create spies for as per createSpyObj docs

    However, SubscribersService uses tableList$ as a property. Because it does this.tableList$ as it's some kind of observable object and doesn't call it like you would do with a method: this.tableList$().

    If you want to create an object with your tableListMock subject for the tableList$ property, you can use the next optional argument for createSpyObj:

    subscribersServiceMock = jasmine.createSpyObj([
          'resetMiscValues',
          'getMiscValues',
          'initPage',
        ], { tableList$: tableListMock$ });
    

    As createSpyObj docs state, the last argument can define properties to creates spies for. Or an object to actually define properties with their values in the object.

    You can also pass an array of method names:

    subscribersServiceMock = jasmine.createSpyObj([
          'resetMiscValues',
          'getMiscValues',
          'initPage',
          'tableList$',
        ], ['tableList$']);
    

    Then, spies will be created for the property accessor. This way you'll be able to spy when tableList$ is get or set.

    Here's how you'd configure the get spy so that subscribersServiceMock.tableList$ returns the mock subject:

    subscribersServiceMock = jasmine.createSpyObj(
     [
       // method names as ususal
     ], 
     ['tableList$']
    );
    const tableList$GetterSpy = Object.getOwnPropertyDescriptor(
      subscribersServiceMock,
      'tableList$',
    )!.get as jasmine.Spy
    tableList$GetterSpy.and.returnValue(mockTableList$)
    

    A bit cumbersome if you just want to set the value, previous way of passing an object is shorter. Though this way you can verify if tableList$ was accessed by using the spy:

    expect(tableList$Getter).toHaveBeenCalled()