Angular with Jasmine
1. Jasmine
2. Arrange, Act, Assert
3. Unit testing Angular
- pipes
- services
- components
- attribute directives
4. Debugging unit tests
import { Injectable } from '@angular/core';
// moment
import * as moment from 'moment';
// services
import { StateService } from '../state/state.service';
import { LoadHelperService } from './helpers/load-helper/load-helper.service';
// models
import { Granularity } from '../../models/granularity.model';
import { LoadSettings } from '../../models/load-settings.model';
@Injectable()
export class LoadService {
private loadObject: LoadConfig = {} as any;
constructor(
private loadHelper: LoadHelperService,
private stateService: StateService,
) { }
// ...
prefillConfig(loadSettings: LoadSettings): void {
if (loadSettings.startDate && loadSettings.endDate && typeof loadSettings.hoursDay === 'number') {
const start = moment(loadSettings.startDate).startOf('d');
const end = moment(loadSettings.endDate).endOf('d');
const hours = loadSettings.hoursDay;
const workDays = this.stateService.workingSettings().value.workingDays;
this.loadObject = {
...loadSettings,
id: loadSettings.id,
load: loadSettings.load,
startDate: moment(loadSettings.startDate).valueOf(),
endDate: moment(loadSettings.endDate).valueOf(),
workDays: this.loadHelper.getWorkDays(start, end, hours, workDays),
workWeeks: this.loadHelper.getWorkPeriod(LoadUnitType.week, start, end, hours, workDays),
workMonths: this.loadHelper.getWorkPeriod(LoadUnitType.month, start, end, hours, workDays),
totalHours: this.loadHelper.getWorkHoursForPeriod(start, end, hours, workDays),
};
this.loadObject.suggestedGranularity = this.suggestGranularity();
}
}
private suggestGranularity(): Granularity {
// ...
}
}
import { TestBed } from '@angular/core/testing';
import { LoadService } from './load.service';
// Models
import { LoadConfig } from '../../models/load-config.model';
import { LoadUnitType } from '../../models/load-unit-type.model';
import { Granularity } from '../../models/granularity.model';
// Services
import { StateService } from '../state/state.service';
import { LoadHelperService } from './helpers/load-helper/load-helper.service';
describe('LoadService', () => {
let loadHelperServiceSpy: jasmine.SpyObj<LoadHelperService>;
let stateServiceSpy: jasmine.SpyObj<StateService>;
let service: LoadService;
const testLoad: LoadConfig = {
id: '1',
startDate: 1,
endDate: 20,
hoursDay: 1,
totalHours: 0,
load: 10,
workMonths: [],
workWeeks: [],
workDays: [],
};
beforeEach(() => {
loadHelperServiceSpy = jasmine.createSpyObj<LoadHelperService>('LoadHelperService', [
'getWorkDays',
'getWorkPeriod',
'getWorkHoursForPeriod',
]);
stateServiceSpy = jasmine.createSpyObj<StateService>('StateService', ['workingSettings']);
TestBed.configureTestingModule({
providers: [
{
provide: LoadHelperService,
useValue: loadHelperServiceSpy,
},
{
provide: StateService,
useValue: stateServiceSpy,
},
LoadService,
],
});
service = TestBed.get(LoadService);
});
it('should exist', () => {
expect(service).toBeTruthy();
});
describe('prefillConfig()', () => {
it('prefills config work', () => {
// Arrange
const settings = {
user: { Id: '1', Name: 'test user' },
load: 100,
hoursDay: 5,
startDate: 100,
endDate: 200,
};
loadHelperServiceSpy.getWorkDays.and.returnValue([]);
loadHelperServiceSpy.getWorkPeriod.and.returnValue([]);
loadHelperServiceSpy.getWorkHoursForPeriod.and.returnValue(0);
stateServiceSpy.workingSettings.and.returnValue({
value: {
workingDays: {},
},
});
spyOn(service, 'suggestGranularity').and.returnValue(Granularity.days);
// Act
service.prefillConfig(settings);
// Assert
expect(service['loadObject'].workDays).toEqual([]);
expect(service['loadObject'].workWeeks).toEqual([]);
expect(service['loadObject'].workMonths).toEqual([]);
expect(service['loadObject'].totalHours).toEqual(0);
expect(service.suggestGranularity).toHaveBeenCalled();
expect(loadHelperServiceSpy.getWorkDays).toHaveBeenCalled();
expect(loadHelperServiceSpy.getWorkPeriod).toHaveBeenCalled();
expect(loadHelperServiceSpy.getWorkHoursForPeriod).toHaveBeenCalled();
});
});
});
@Component({
selector: 'cc-select',
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
providers: [SELECT_VALUE_ACCESSOR],
})
export class SelectComponent implements ControlValueAccessor, OnInit {
@Input() options: SelectOption[] = [];
selectedOption: SelectOption;
onChange;
onTouched;
ngOnInit() {
this.selectedOption = this.options[0];
}
selectOption(option: SelectOption): void {
this.onChange(option.value);
this.selectedOption = option;
}
writeValue(formFieldValue: string): void {
this.selectedOption = this.options.find((option) => option.value === formFieldValue);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectComponent } from './select.component';
describe('SelectComponent', () => {
let component: SelectComponent;
let fixture: ComponentFixture<SelectComponent>;
const options = [
{ value: 'days', label: 'Days' },
{ value: 'weeks', label: 'Weeks' },
];
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
SelectComponent,
],
});
fixture = TestBed.createComponent(SelectComponent);
component = fixture.componentInstance;
component.options = options;
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe(`selectOption()`, () => {
beforeEach(() => {
component.onChange = () => {};
spyOn(component, 'onChange');
});
it(`sets selectedOption variable`, () => {
component.selectOption(options[1]);
expect(component.selectedOption).toEqual(options[1]);
});
it(`calls onChange with option value`, () => {
component.selectOption(options[1]);
expect(component.onChange).toHaveBeenCalledWith(options[1].value);
});
});
describe(`writeValue()`, () => {
it(`sets selectedOption`, () => {
component.writeValue('days');
expect(component.selectedOption).toEqual(options[0]);
});
});
});
import { ForbiddenKeyCodesDirective } from './forbidden-key-codes.directive';
describe('ForbiddenKeyCodesDirective', () => {
let directive: ForbiddenKeyCodesDirective;
beforeEach(() => {
directive = new ForbiddenKeyCodesDirective();
});
describe(`onKeyDown()`, () => {
const event = {
preventDefault() {},
stopImmediatePropagation() {},
key: '+',
} as any;
beforeEach(() => {
spyOn(event, 'preventDefault');
spyOn(event, 'stopImmediatePropagation');
});
it(`prevents event if key code is forbidden`, () => {
directive.ccForbiddenKeyCodes = ['+', '-'];
directive.onKeyDown(event);
expect(event.preventDefault).toHaveBeenCalled();
expect(event.stopImmediatePropagation).toHaveBeenCalled();
});
it(`doesn't prevent event if key code is allowed`, () => {
directive.ccForbiddenKeyCodes = ['-'];
directive.onKeyDown(event);
expect(event.preventDefault).not.toHaveBeenCalled();
expect(event.stopImmediatePropagation).not.toHaveBeenCalled();
});
});
});
oleksandr.hutsulyak@techmagic.co
kami_lviv