2

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;
}
tryingHard
  • 1,794
  • 4
  • 35
  • 74
  • @AndrewAllen I have tested it, by changing the line you mentioned I get the same error saying `Error: Expected undefined to equal Object(...)` – tryingHard Sep 28 '22 at 17:48
  • @AndrewAllen I have updated question by providng full list of depedencies and devDepedencies – tryingHard Sep 28 '22 at 17:55
  • @AndrewAllen i removed `ngrx` `tag` which you added – tryingHard Sep 28 '22 at 18:13
  • Don't know if it's possible. Maybe it's better though to test the actual desired functionality of the component? What happens with that private lts property? Can't you test that? Unit testing the internal workings itself may not be a good idea. – johey Oct 03 '22 at 14:30
  • @johey My goal is to make more test coverage. I can accept an answer if the `lts` was `public`. It's hard for me to test this code when it happens after `pipe` and `subscribe`. For the private field testing you can read here: https://stackoverflow.com/a/46601873/4983983 that it is a viable option. In real the part in `subscribe` has many lines of code that I want to cover in tests. – tryingHard Oct 03 '22 at 15:39
  • Test coverage isn't an holy grail. By blindly testing everything I think you may greatly increase the cost and burden of your test suite. But it may be a personal opinion. – johey Oct 04 '22 at 08:51
  • @johey sure, my goal is to reach 50% of total coverage and it's not that much. I agree that aiming for example for 100% is nonsense. Now i have like 43% total coverage and what i mentioned in previous comment has impact to reach 50%. – tryingHard Oct 04 '22 at 08:54
  • You need to include the code that actually uses the `lts` field, otherwise we can't help you test it. – skink Oct 05 '22 at 11:40
  • @skink I want to cover the lines in `subscribe` in `constructor` how can I do it? `lts` can be even public - still I can not run this code in my test. When I added `console.log` in this `subscribe` it does not print out. I mean even in the version from `answer`. – tryingHard Oct 06 '22 at 12:27
  • Alright. What does the `Dispatcher` class look like? Must be something you maintain yourself? Normally the code in the answer should work so I would suspect there's some special treatment necessary for the `Dispatcher` class. – skink Oct 07 '22 at 08:08
  • 1
    @skink I have edited the question and I have added `Dispatcher` class code. – tryingHard Oct 07 '22 at 08:12
  • I made a StackBlitz and it looks like the answer actually works: https://stackblitz.com/edit/angular-aiavfv-mojgat?file=src%2Fapp%2Fapp.component.spec.ts – skink Oct 07 '22 at 09:26
  • @skink Thanks for StackBlitz. I see the same error, see here: https://imgur.com/a/nVeqJdB – tryingHard Oct 07 '22 at 09:29
  • 1
    Yep, that's the error from the original test. There are two tests in the StackBlitz — the original one and the one from the answer. You can see them if you click the "Spec List" link. – skink Oct 07 '22 at 09:31
  • 1
    @skink Thanks - clearly some additional logic in constructor after this line `this.lts = st;` make this test to not work for me now. – tryingHard Oct 07 '22 at 10:50

1 Answers1

3

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
      }));
}
  • I have tested it, while I thinkt it's a good idea unfortunately the field `lts` is still `undefined` even if I get rid of `private` and check by `expect(cmp.lastTileSelected).toBe(null);` I tried `flush` ans also `tick` methods. – tryingHard Oct 05 '22 at 10:19
  • You set a spy on the dispatcher, unless i'm wrong once you have done that the original method won't be called. Can you try to remove it ? – Djebarri Amazigh Oct 05 '22 at 10:57
  • I have tested when it's commented but result is the same. In subscribe I have added log saying `in subscribe` but it does not appear. Maybe cause the test is wrong? This section is in the constructor section in the main code. – tryingHard Oct 05 '22 at 15:30