Skip to content

Retrospective #3

Tuong-Nguyen edited this page Oct 9, 2017 · 11 revisions

Testing rules (updated)

  • No need to test the connection between container and presentational component, ex: container pass widget as input of presentational component and we expect the presentational component has that widget as input.

Attribute Directive

  • Issue: Testing attribute directive with component in playground
  • Solution: Declare both directive and component
export default sandboxOf(PageHeaderComponent, {
  imports: [
    CommonModule,
    MdMenuModule,
    MdButtonModule,
    MdToolbarModule,
    MdIconModule
  ],
  declarations: [RwHighlightDirective, PageHeaderComponent]
}).add('display default', {
  template: `<app-page-header [pageTitle]="'Songoku'">`,
});

State is undefined when navigate to other page

  • Issue: slice of state is undefined when navigate to other page
  • Root cause: Do not return old state in case the action is not defined
  • Solution: Reducer must have default case which returns the old state.

Using async pipe

Async pipe is used with Observable or Promise. When Observable has not emitted yet (or Promise has not returned yet), async pipe returns null. Otherwise, it returns the emitted object.
Following expression can be used for getting a property of returned object: (observable$ | async)?.id || ''. It returns:

  • id of emitted object
  • '' if object has not emitted yet. (default value)
<app-header
            [userName]="(loginInformation$ | async)?.displayName || ''"
            [currentLocation]="currentLocation"
            [routerList]="routerList"
            (logout)="logoutUser($event)">
</app-header>

Testing with async pipe

To ensure the component receive correct value when Observable emits data. We can use Subject class as the input Observable (then we can use subject.next() to emit data).
In the test, we must use fixture.autoDetectChanges(true) so that the component can receive the correct data.

it('should update userName of HeaderContainerComponent when loginInformation is changed', () => {
      // Auto detect changes: enabled
      fixture.autoDetectChanges(true);

      component = fixture.componentInstance;
      const fixtureHeaderComponent = fixture.debugElement.query(By.directive(HeaderComponent));
      const headerComponent = fixtureHeaderComponent.componentInstance;

      expect(headerComponent.userName).toBe(null);

      // emit new item
      loginInformationSubject.next({id: '10', displayName: 'User Info'});
      fixture.detectChanges();

      expect(headerComponent.userName).toBe('User Info');
    });

ngrx/store: Current State

Store is an BehaviorSubject (ie: it is also a Observable and Observer and when it is subscribed, it emits the last emitted items to the observer).
When a component subscribes to Store, it will receive the current State.

Effect testing

  • Fact: Have to define mock class TestActions
  • Update: use provideMockAction from @ngrx/store/testing
 // ...
 let actions: Observable<any>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        MyEffects,
        provideMockActions(() => actions),
        // other providers
      ],
    });
    // ...
  });

  it('should work', () => {
    actions = hot('--a-', { a: SomeAction });
    // ...
  });

RxJS Testing

Jasmine Marble can be used for testing operators and combination of operators. (Link)[RxJS-Testing-&-Experimental]

Lodash

Lodash is a library which is great for:

  • Iterating arrays, objects, & strings
  • Manipulating & testing values
  • Creating composite functions

It is currently integrated into the project. Therefore, when you have to do somethings above, please consider to use Lodash before implementing it yourself. There may be function in Lodash or it can be done easily using Lodash functions.

Documentation

ngrx/effect

switchMap vs. flatMap: Please be careful with switchMap, as soon as the second item is emitted, previous Observables are ignore. It is suitable for READ but flatMap is suitable for ADD - DELETE - UPDATE

Reference:
http://reactivex.io/documentation/operators/flatmap.html
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-switchMap

rxjs Unsubscribe

Remember to unsubscribe Observable to avoid memory leak.

  • Async Pipe: it automatically unsubscribes when the component is destroyed.
  • Component: ngOnDestroy is where we should unsubscribe the observable

How to unsubscribe:

  • Use subscription
getUser(id: string) {
  const subscription = this.userService.get(id)
    .subscribe(user => {
      this.user = user;
    });
  this.subscription.add(subscription);
}

ngOnDestroy() {
  this.subscription.unsubscribe();
}
  • Use takeWhile
export class MyComponent implements OnDestroy, OnInit {

  public user: User;
  private alive: boolean = true;

  public ngOnInit() {
    this.userService.authenticate(email, password)
      .takeWhile(() => this.alive)
      .subscribe(user => {
        this.user = user;
      });
  }

  public ngOnDestroy() {
    this.alive = false;
  }
}
  • Use takeUntil
import "rxjs/add/operator/takeUntil";
import { Subject } from "rxjs/Subject";

export class MyComponent implements OnDestroy, OnInit {

  public user: User;
  private unsubscribe: Subject<void> = new Subject(void);

  public ngOnInit() {
    this.userService.authenticate(email, password)
      .takeUntil(this.unsubscribe)
      .subscribe(user => {
        this.user = user;
      });
  }

  public ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }
}

Reference: http://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/

Clone this wiki locally