Components

All Akita's operations either from the store or the query, are synchronicity, therefore more convenient to test.

Let's create a simple todos application so we can have something to work it.

todos.store.ts
export interface TodosState extends EntityState<Todo> {
ui: {
filter: string;
};
}
@StoreConfig({ name: 'todos' })
export class TodosStore extends EntityStore<TodosState> {
constructor() {
super({ ui: { filter: 'active' } });
}
}
todos.query.ts
export class TodosQuery extends QueryEntity<TodosState> {
selectFilter$ = this.select(state => state.ui.filter);
constructor(protected store: TodosStore) {
super(store);
}
}
todos.component.ts
@Component({
selector: 'app-todos',
template: `
<ul>
<li *ngFor="let todo of todos$ | async">
{{todo.title}}
</li>
</ul>
<div class="no-todos" *ngIf="!(todos$ | async)?.length">
Empty todos
</div>
<div class="filter">{{ selectFilter$ | async }}</div>
`
})
export class TodosComponent implements OnInit {
todos$: Observable<Todo[]>;
selectFilter$: Observable<string>;
constructor(
private todosQuery: TodosQuery,
private todosService: TodosService
) {}
ngOnInit() {
this.todos$ = this.todosQuery.selectAll();
this.selectFilter$ = this.todosQuery.selectFilter$;
this.todosService.get();
}
}

There are two strategies that you can use to test your component, mock the query or use the store directly.

Using the Store

The first strategy is to update the store as you'll typically do and make the correct assertions.

todos.component.spec.ts
describe('TodosComponent - Using Store', () => {
let component: TodosComponent;
let todosStore: TodosStore;
let fixture: ComponentFixture<TodosComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
{
provide: TodosService,
useValue: jasmine.createSpyObj('TodosService', ['get'])
}
],
declarations: [TodosComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodosComponent);
component = fixture.componentInstance;
todosStore = TestBed.get(TodosStore);
});
it('should display no todos message', () => {
fixture.detectChanges();
const noMessageElement = fixture.debugElement.query(By.css('.no-todos'));
expect(noMessageElement).not.toBeNull();
});
it('should display two todos', () => {
todosStore.set([createTodo(), createTodo()]);
fixture.detectChanges();
const todos = fixture.debugElement.queryAll(By.css('li'));
expect(todos.length).toEqual(2);
});
it('should display the initial filter', () => {
fixture.detectChanges();
const filter = fixture.debugElement.query(By.css('.filter'));
expect(filter.nativeElement.innerText).toEqual('active');
});
it('should display the updated filter', () => {
todosStore.updateRoot({ ui: { filter: 'completed ' } });
fixture.detectChanges();
const filter = fixture.debugElement.query(By.css('.filter'));
expect(filter.nativeElement.innerText).toEqual('completed');
});
});

Mocking the Query

The second strategy is mocking the required query methods and make the correct assertions.

todos.component.spec.ts
describe('TodosComponent - Using Query', () => {
let component: TodosComponent;
let todosQuery: jasmine.SpyObj<TodosQuery>;
let fixture: ComponentFixture<TodosComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
{
provide: TodosService,
useValue: jasmine.createSpyObj('TodosService', ['get'])
},
{
provide: TodosQuery,
useValue: jasmine.createSpyObj('TodosQuery', ['selectAll'])
}
],
declarations: [TodosComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodosComponent);
component = fixture.componentInstance;
todosQuery = TestBed.get(TodosQuery);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display no todos message', () => {
todosQuery.selectAll.and.returnValue(of([]));
fixture.detectChanges();
const noMessageElement = fixture.debugElement.query(By.css('.no-todos'));
expect(noMessageElement).not.toBeNull();
});
it('should display two todos', () => {
todosQuery.selectAll.and.returnValue(of([createTodo(), createTodo()]));
fixture.detectChanges();
const todos = fixture.debugElement.queryAll(By.css('li'));
expect(todos.length).toEqual(2);
});
it('should display the initial filter', () => {
(todosQuery as any).selectFilter$ = of('active');
fixture.detectChanges();
const filter = fixture.debugElement.query(By.css('.filter'));
expect(filter.nativeElement.innerText).toEqual('active');
});
it('should display the updated filter', () => {
(todosQuery as any).selectFilter$ = of('completed');
fixture.detectChanges();
const filter = fixture.debugElement.query(By.css('.filter'));
expect(filter.nativeElement.innerText).toEqual('completed');
});
});

When you choose one of the strategies, make sure to be consistent.