expression changed after checked


这是一个非常常见的错误,点击到html的78行(实现一个errorHandler去log error.stack的重要性), 可以看到可能出错的几个绑定。最后通过排除法确定是一个属性的绑定(只能通过排除法,这一行html有好几个绑定)

<app-add-account-holder [expandMode]=”clientExpandMode” [isNewHousehold]=”household.isNew” [advisor]=”advisorSelector?.selectedAdvisor” (advisorErrorChange) =”onAdvisorError($event)”></app-add-account-holder>

问题出在ts代码的一个observable的subcription里

        this.showAdvisor = false;
       // this.advisorSelector = null;
代码执行完后,View会进行Change Detection and Binding。于是showAdvisor = false的后果就来了:
由于html里有<div *ngIf=”showAdvisor”
ts代码里的AdvisorSelectorComponent整个就消失了,ViewChild的static:false的作用就是同步更新这种时隐时现,特别是初始时不存在的界面元素的。
  @ViewChild(“advisorSelector”, { static: false })
  advisorSelector?: AdvisorSelectorComponent;
但这个场景下,由于showAdvisor 的变化,属性advisorSelector也就变成了null. 但这个变化是发生在Change Detection and Binding之后的。
Angular本不需要尝试再次绑定[advisorSelector]的。这完全是为了提醒码农你的代码里有属性发生了二次更新,这是危险的因为用户看到的不再是最新的属性绑定结果。但这个出错仅显示在console log,并不会真的crash.

Using CreateSpyObject with NGRX store testing

1. in the whole describe() lambda function, define below handles that can be referenced in all tests.

// use SpyObj because we want to spy multiple methods of the object
const testStore = jasmine.createSpyObj('Store', ['select', 'dispatch', 'pipe']);
// used to mock receving dispatched action     
const actions$: ReplaySubject<any> = new ReplaySubject();
let fixture : ComponentFixture<CurrentComponent>;
let component: CurrentComponent;

2.  both sync and async version of beforeEach happens before It()

beforeEach(async(() => {
   ......
  TestBed.configureTestingModule({       declarations: [XComponent, YComponent, ......],       providers: [XService, YService, // normal injection         provideMockActions(() => actions$),
           {           provide: Store, useValue: testStore         },
            ]
    })
 .compileComponents(); //compileComponents is async method
}));

beforeEach(()=>{
    testStore.dispatch.and.returnValue(true);
    testStore.select.and.callFake(()=>Observable.of(false));
    testStore.pipe.and.callFake(()=>Observable.of(true));
   // the store just gave some random objects so component won't throw exception
    fixture = TestBed.createComponent(CurrentComponent);
    component = fixture.componentInstance;
});
3. the tests Itself
It('some test', ()=> {
  action$.next({type: '[Action Name] Action Description', payload: {      success: true}
  });  // mocking the component getting an action from the store.
  component.someField = true; // because we called faked "select" and "pipe" spy methods, so we have to manually do the job.
  fixture.detectChanges();
  const compiled = fixture.debugElement.nativeElement;
  expect(compile.querySelector(".elementId")).toBeTruthy();
  expect(testStore.dispatch).toHaveBeenCalledWith(new SomeAction(...));
});

the drawback of this method is “component.someField = true;”, this is NOT really testing the NGRX action pipe/select and subscription method content.

to fix that, “testStore.select.and.callFake(()=>Observable.of(false));” need to change to  fake something that will really fit and be passed to the subscription method. like

()=>Observable.of({the projection of appState});