Search code examples
angularjasmineangular-test

How to test private field in subscribe of dispatcher in Angular?


I have file:

@Component({
  selector: 'page',
  templateUrl: './page.html',
  styleUrls: ['./page.scss']
})
export class PageComponent implements OnInit, OnDestroy {
  private lts: Igst;
  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private dispatcher: Dispatcher
    ) {
    this.dispatcher
      .ofType(ProductsPageActions.TILE_UPDATE_STATE)
      .pipe(
        map((action: ProductsPageActions.St) => action.payload.st),
        takeUntil(this.componentDestroyed$)
      )
      .subscribe((st: Igst) => {
        this.lts = st;
      }
  }

  handleTs(st: Igst): void {
    this.dispatcher.dispatch(new ProductsPageActions.St({st}));
  }
}

And i want to make more test coverage

How can i test coverage this line: this.lts = st; ?

I tried this:

describe('Testing pageComponent', () => {
 let cmp: PageComponent;
 let fixture: ComponentFixture<PageComponent>;
 let dispatcher: Dispatcher;

 beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, RouterTestingModule],
      schemas: [NO_ERRORS_SCHEMA],
      providers: [
        {provide: ActivatedRoute, useValue: {params: of({section: 'test'})}},
        Dispatcher
      ]
    });


      it('should handleTileSelect', () => {
        spyOn(dispatcher, 'dispatch');
        const payload = {
            agrType: 'L',
        };
        cmp.handleTs(payload); // payload is st
    
        expect(dispatcher.dispatch).toHaveBeenCalledTimes(1); // ok
    
        expect(cmp['lts']).toEqual(payload); // lts is undefined
      });
}

Package.json consist of:

"dependencies": {
    "@angular-devkit/schematics": "^13.3.0",
    "@angular/animations": "^13.3.0",
    "@angular/cdk": "^12.2.13",
    "@angular/common": "^13.3.0",
    "@angular/compiler": "^13.3.0",
    "@angular/core": "^13.3.0",
    "@angular/forms": "^13.3.0",
    "@angular/localize": "^13.3.0",
    "@angular/material": "^12.2.13",
    "@angular/material-moment-adapter": "^12.2.13",
    "@angular/platform-browser": "^13.3.0",
    "@angular/platform-browser-dynamic": "^13.3.0",
    "@angular/router": "^13.3.0",
    "@auth0/angular-jwt": "^2.0.0",
    "@fortawesome/fontawesome-free": "^5.2.0",
    "@stomp/ng2-stompjs": "^7.0.0",
    "@stomp/stompjs": "^4.0.8",
    "@types/lodash": "^4.14.116",
    "@types/requirejs": "^2.1.31",
    "chokidar": "^3.5.3",
    "core-js": "^3.6.5",
    "deep-freeze": "0.0.1",
    "lodash": "^4.17.11",
    "moment": "^2.22.2",
    "ng2-cache-service": "^1.1.1",
    "ngx-perfect-scrollbar": "^8.0.0",
    "reselect": "^3.0.1",
    "rxjs": "^6.5.5",
    "zone.js": "~0.11.4"
  },

"devDependencies": {
    "@angular-devkit/build-angular": "~13.3.0",
    "@angular-eslint/builder": "13.1.0",
    "@angular-eslint/eslint-plugin": "13.1.0",
    "@angular-eslint/eslint-plugin-template": "13.1.0",
    "@angular-eslint/schematics": "13.1.0",
    "@angular-eslint/template-parser": "13.1.0",
    "@angular/cli": "~13.3.0",
    "@angular/compiler-cli": "^13.3.0",
    "@angular/language-service": "^13.3.0",
    "@types/jasmine": "~3.6.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^8.10.50",
    "@typescript-eslint/eslint-plugin": "5.11.0",
    "@typescript-eslint/parser": "5.11.0",
    "coa": "2.0.2",
    "eslint": "^8.11.0",
    "eslint-plugin-jasmine": "^4.1.3",
    "eslint-plugin-rxjs": "^5.0.2",
    "highlight.js": "^11.5.0",
    "husky": "^1.1.3",
    "jasmine": "^4.0.2",
    "jasmine-core": "^4.0.1",
    "jasmine-spec-reporter": "~5.0.0",
    "json-server": "^0.17.0",
    "karma": "^6.3.17",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "^2.2.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "karma-junit-reporter": "^1.2.0",
    "karma-spec-reporter": "^0.0.34",
    "ngx-highlightjs": "^4.1.4",
    "ts-node": "~7.0.0",
    "tslib": "^2.3.1",
    "typescript": "~4.5.5"
  }

EDIT: I have added Dispatcher class:

import {IAction} from './action';
import {filter} from 'rxjs/operators';
import {Observable, Subject} from 'rxjs';
export class Dispatcher extends Subject<IAction> {
  dispatch(action: IAction): void {
    super.next(action);
  }

  ofType(type: string): Observable<IAction> {
    return this.pipe(filter(action => action.type === type));
  }
}

export const ofType = (type: string) => (source: Observable<IAction>) =>
  new Observable(observer => {
    source
      .pipe(
        filter((action: IAction) => action.type === type)
      ).subscribe(
      (data) => {
        observer.next(data);
      },
      (err) => {
        observer.error(err);
      },
      () => {
        observer.complete();
      }
    );
  });

IAction:

export interface IAction {
  readonly type: string;
  readonly reducerId?: string;
  payload?: any;
}

Solution

  • Subscribe is asynchronous by the time you are accessing lts the observable wasn't flushed. Try something like:

    describe('Testing pageComponent', () => {
     let cmp: PageComponent;
     let fixture: ComponentFixture<PageComponent>;
     let dispatcher: Dispatcher;
    
     beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule, RouterTestingModule],
          schemas: [NO_ERRORS_SCHEMA],
          providers: [
            {provide: ActivatedRoute, useValue: {params: of({section: 'test'})}},
            Dispatcher
          ]
        });
    
    
          it('should handleTileSelect', fakeAsync(() => {
            //spyOn(dispatcher, 'dispatch'); // remove me
            const payload = {
                agrType: 'L',
            };
            cmp.handleTs(payload); // payload is st
        
            // expect(dispatcher.dispatch).toHaveBeenCalledTimes(1); // ok
            flush(); // or tick();
            expect(cmp['lts']).toEqual(payload); // lts is undefined
          }));
    }