我正在建立一个网站来维护一些设备。有一种反应形式来创建新设备,因为我需要在多个页面上,我使用设备FormGroup创建了一个专用组件。这样我就可以以其他形式重用它。该设备还有一个访问控制列表(acl),其中包含确定谁可以访问设备以及谁不能访问设备的规则。此acl还用于位置和用户。所以我制作了一个acl-form组件,以便能够重用这个表单。此acl表单填充formArray。
acl-form有2个额外的函数,prepareACL和sync。第一个函数用于为访问控制条目创建FormControl,并使用数据填充它们。第二个函数用于进行一些数据操作,因为表单中的数据不能按原样发送到服务器。
这一切都很好,但现在我想为表单编写一些测试。测试acl-form本身没问题,因为所有的依赖都可以很容易地被模拟。另一方面,设备形式要困难得多。当我编写测试时,如果我模拟了设备形式的所有依赖关系(正常)和acl-form的依赖关系,我只能让它工作。但由于acl-form已经自行测试,我不想打扰它。
我的问题是,如何模拟acl-form,所以我不需要提供所有依赖项,我如何监视prepareACL-function和sync-function,以确保它们被调用?
这是我的代码:
ACL-form.component.ts:
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormArray, Validators } from '@angular/forms';
import { UserService } from '../../../services/user.service';
@Component({
selector: 'acl-form',
templateUrl: './acl-form.component.html'
})
export class ACLFormComponent implements OnInit {
@Input()
public group: FormArray;
public users: Object[];
public formErrors = {};
build() {
let group = this.fb.array([]);
return group;
}
constructor(private fb: FormBuilder, private userService: UserService) { }
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
buildACE() {
let group = this.fb.group({
guid: ['', []],
user: ['', [Validators.required]],
permission: ['', []],
read: [{ value: '', disabled: true }, []],
write: [{ value: '', disabled: true }, []],
execute: [{ value: '', disabled: true }, []]
});
group.controls['user'].valueChanges.subscribe(guid => {
if (guid && guid !== '') {
group.controls['read'].enable();
group.controls['write'].enable();
group.controls['execute'].enable();
} else {
group.controls['read'].disable();
group.controls['write'].disable();
group.controls['execute'].disable();
}
});
return group;
}
createACEs(count: Number) {
const control = this.group;
while (control.length) {
control.removeAt(0);
}
for (let i = 0; i < count; i++) {
control.push(this.buildACE());
}
}
addACE() {
this.group.push(this.buildACE());
}
removeACE(i: number) {
this.group.removeAt(i);
}
encodePermission(ace) {
let read = ace.read;
let write = ace.write;
let execute = ace.execute;
let permission = 0;
permission += read ? 4 : 0;
permission += write ? 2 : 0;
permission += execute ? 1 : 0;
ace.permission = permission;
}
decodePermission(ace) {
let permission = ace.permission;
ace.read = (permission & 4) > 0;
ace.write = (permission & 2) > 0;
ace.execute = (permission & 1) > 0;
}
encodePermissions() {
let acl = this.group.value;
for (let i = 0; i < acl.length; i++) {
this.encodePermission(acl[i]);
// remove secondary fields to`enter code here` prevent api necking about it
delete acl[i].read;
delete acl[i].write;
delete acl[i].execute;
// remove guid to prevent api necking about it
if (acl[i].guid === '') {
delete acl[i].guid;
}
}
}
decodePermissions(acl) {
for (let i = 0; i < acl.length; i++) {
this.decodePermission(acl[i]);
}
}
prepareACL(acl) {
this.createACEs(acl.length);
this.decodePermissions(acl);
}
// assign removedACE to the entity
handleRemovedACE(entity) {
let acl = this.group.value;
if (entity.acl) {
// remove acl
acl = acl.filter(x => x.permission > 0);
entity.removedACE = [...entity.acl].filter(x => acl.find(y => y.guid === x['guid']) === undefined)
.map(x => x['guid']);
} else {
console.error('no acl entry found');
}
entity.acl = acl;
}
sync(entity) {
this.encodePermissions();
this.handleRemovedACE(entity);
}
}
device.component.ts:
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { EnvironmentService } from '../../../services/environment.service';
import { ResidenceService } from '../../../services/residence.service';
import { DeviceService } from '../../../services/device.service';
import { FormUtility } from '../../utility/form-utility';
import { macValidator } from '../../validators/global.validators';
import { ACLFormComponent } from '../acl-form/acl-form.component';
import { NetworkConfigFormComponent } from '../../components/network-config-form/network-config-form.component';
@Component({
selector: 'device-form',
templateUrl: './device-form.component.html'
})
export class DeviceFormComponent implements OnInit {
@ViewChild(ACLFormComponent)
public aclForm: ACLFormComponent;
@ViewChild(NetworkConfigFormComponent)
public networkConfigForm: NetworkConfigFormComponent;
@Input()
public group: FormGroup;
public states: String[];
public types: Object[];
public environments: Object[];
public residences: Object[];
private residence: Object;
public rooms: Object[];
private onValueChanged: Function;
public formErrors = {};
private validationMessages = {
mac: {
required: 'mac address is required',
mac: 'Ivalid mac address'
}
};
constructor(
private fb: FormBuilder,
private deviceService: DeviceService,
private environmentService: EnvironmentService,
private residenceService: ResidenceService
) { }
ngOnInit() {
this.deviceService.getDeviceStates().subscribe(states => {
this.states = states;
});
this.deviceService.getDeviceTypes().subscribe(types => {
this.types = types;
});
this.environmentService.getEnvironmentList().subscribe(envs => {
this.environments = envs;
});
this.onValueChanged = FormUtility.valueChangeGenerator(this.group, this.formErrors, this.validationMessages);
this.group.valueChanges.subscribe(data => {
this.onValueChanged(data);
});
this.group.controls['environment'].valueChanges.subscribe(data => {
this.residenceService.getResidencesInEnvironmentList(data).subscribe(res => {
this.group.controls['residence'].enable();
this.residences = res;
// add empty residence to make it possible only select environment
let emptyRes = {name: '-', guid: ''};
this.residences.push(emptyRes);
});
});
this.group.controls['residence'].valueChanges.subscribe(data => {
this.residenceService.getResidence(data).subscribe(res => {
this.group.controls['room'].enable();
this.residence = res;
this.rooms = res.room;
if (this.rooms) {
// add empty room to make it possible only select residence and environment
this.rooms.push({comment: '-', guid: ''});
}
});
});
}
build() {
return this.fb.group({
mac: [
'', [
Validators.required,
macValidator
]
],
status: [
'', []
],
type: [
'', []
],
network: this.fb.group({}),
environment: [
'', [
Validators.required
],
],
residence: [
{
value: '',
disabled: true
}, [
]
],
room: [
{
value: '',
disabled: true
}, [
]
],
acl: this.fb.group({})
});
}
sync(device) {
// encode befor getting group.value because encode is working on the formdata itself
// encode permissions rwx => 1-7 and remove entries with permission === 0
this.aclForm.sync(device);
// handle parent_path
let formdata = this.group.value;
device.parent_path = ',' + formdata.environment + ',' + formdata.residence + ',' + formdata.room + ',';
}
patchValue(entity) {
this.aclForm.prepareACL(entity.acl);
// get parent_path objects
if (entity.parent_path && entity.parent_path !== '') {
let chunks = entity.parent_path.split(',');
if (chunks.length === 5) {
entity.environment = chunks[1];
entity.residence = chunks[2];
entity.room = chunks[3];
}
}
this.group.patchValue(entity);
}
}
我会使用ng-mocks的MockComponent
阅读我的示例代码显示了使用它的一种方法。您仍然需要导入主要组件,以便模拟器可以读取它的签名。
从自述文件: -
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockModule } from 'ng-mocks';
import { DependencyModule } from './dependency.module';
import { TestedComponent } from './tested.component';
describe('MockModule', () => {
let fixture: ComponentFixture<TestedComponent>;
let component: TestedComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TestedComponent,
],
imports: [
MockModule(DependencyModule),
],
});
fixture = TestBed.createComponent(TestedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('renders nothing without any error', () => {
expect(component).toBeTruthy();
});
});
(这个问题可能是重复的;我似乎记得我在这里学过ng-mocks;但我现在没有看到另一个问题)