Unit testing a component that is getting data from Angular Replay Subject.

huangapple go评论54阅读模式
英文:

Unit testing a compnent that is getting data from Angular Replay Subject

问题

以下是您的翻译部分:

我有一个名为EmployeeService的服务,我在其中使用Angular的ReplaySubject从api获取数据。我正在从另一个组件中订阅这个ReplaySubject,现在我正在尝试对其进行测试。但是因为我无法模拟上述服务中的数据,单元测试无法正常工作。

这是EmployeeService的基本结构:

class EmployeeService extends ApiService {

  public currentEmployeeSub: ReplaySubject<EmployeeModel> = new ReplaySubject<EmployeeModel>(1);
  private currentEmployee: EmployeeModel;
  private uuid: UuidService;

  constructor(
    auth: AuthService,
    http: HttpClient,
    uuid: UuidService
  ) {
    super('employees', auth, http);
    this.uuid = uuid;
  }

  // 其他方法
}

这是一个也用于解析数据的utilService:

class UtilService {
  parse(object: any) {
    return JSON.parse(JSON.stringify(object));
  }
}

现在这是我正在尝试测试的组件。请注意,这是一个庞大的组件,但我只展示了最重要的部分,即来自EmployeeService的员工数据。

class SalaryComponent implements OnInit, OnDestroy, AfterViewInit {

  private utilService: UtilService;
  private employeeService: EmployeeService;

  employee: EmployeeModel;
  employeeSubscription: Subscription;

  constructor(utilService: UtilService, employeeService: EmployeeService
  ) {
    this.utilService = utilService;
    this.employeeService = employeeService;

    this.employeeSubscription = this.employeeService.currentEmployeeSub.subscribe((employee: EmployeeModel) => {
      this.employee = this.utilService.parse(employee);
    });
}

上述组件订阅了EmployeeService中的ReplaySubject,获取员工数据并在解析数据后设置this.employee。我只需要在测试规格中为this.employee设置数据,以便我可以对依赖于此数据的组件进行单元测试,如下所示:

<div class="cb-col-12">
   <dropdown
      *ngIf="employee"
      name="employee"
      formControlName="employee"
      (handlerSelect)="onEmployeeChange($event)"
      [options]="employeeOptions"
   >
   </dropdown>
</div>

这是测试规格的外观:

describe('SalaryComponent', () => {
  let component: SalaryComponent;
  let fixture: ComponentFixture<SalaryComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [],
      providers: [],
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(SalaryComponent);
    component = fixture.componentInstance;
  });

  fit('should show drop down if there is employee data', async ()  => {
    fixture.detectChanges();
    await fixture.whenStable();
  
    component.employee = {
      name: 'John Doe',
      age: 23,
      qualifications: [],
      address: {},
    }

    fixture.detectChanges();
    await fixture.whenStable();

    const hostElement: HTMLElement = fixture.nativeElement;
    const dropDown: HTMLInputElement = hostElement.querySelector(
      'dropdown[name="employee"]'
    );

    expect(dropDown).toBeTruthy();
  } 
}

即使在组件中设置了员工数据,我仍然得到下拉元素的值为null

我如何模拟ReplaySubject以设置组件中的员工数据,以便其生效?

英文:

I have this service called EmployeeService where I am using Angular's ReplaySubject to get data from an api. I'm subcribing to this ReplaySubject from another component which I am trying to test now. But because I'm unable to mock the data from the service mentioned above, the unit test is not working.

This is a bare bones of the EmployeeService:

class EmployeeService extends ApiService {

  public currentEmployeeSub: ReplaySubject&lt;EmployeeModel&gt; = new ReplaySubject&lt;EmployeeModel&gt;(1);
  private currentEmployee: EmployeeModel;
  private uuid: UuidService;

  constructor(
    auth: AuthService,
    http: HttpClient,
    uuid: UuidService
  ) {
    super(&#39;employees&#39;, auth, http);
    this.uuid = uuid;
  }

  // other methods
}

This is a utilService which is also being used to parse the data:

class UtilService {
  parse(object: any) {
    return JSON.parse(JSON.stringify(object));
  }
}

Now this is the component I'm trying to test. Please note that this is a huge component but I'm just showing the most important part which is needed for this component to work and that is the employee data coming from EmployeeService.

class SalaryComponent implements OnInit, OnDestroy, AfterViewInit {

  private utilService: UtilService;
  private employeeService: EmployeeService;

  employee: EmployeeModel;
  employeeSubscription: Subscription;

  constructor(utilService: UtilService, employeeService: EmployeeService
  ) {
    this.utilService = utilService;
    this.employeeService = employeeService;

    this.employeeSubscription = this.employeeService.currentEmployeeSub.subscribe((employee: EmployeeModel) =&gt; {
      this.employee = this.utilService.parse(employee);
    });
}

The above component is subscribing to ReplaySubject in EmployeeService, gets the data of the employee and sets this.employee after parsing the data. I just need to set the data for this.employee in the test spec so that I can unit test a component which relies on this data like this:

&lt;div class=&quot;cb-col-12&quot;&gt;
   &lt;dropdown
      *ngIf=&quot;employee&quot;
      name=&quot;employee&quot;
      formControlName=&quot;employee&quot;
      (handlerSelect)=&quot;onEmployeeChange($event)&quot;
      [options]=&quot;employeeOptions&quot;
   &gt;
   &lt;/dropdown&gt;
&lt;/div&gt;

This is how the test spec look like:

describe(&#39;SalaryComponent&#39;, () =&gt; {
  let component: SalaryComponent;
  let fixture: ComponentFixture&lt;SalaryComponent&gt;;

  beforeEach(async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [],
      providers: [],
    })
      .compileComponents();
  });

  beforeEach(() =&gt; {
    fixture = TestBed.createComponent(SalaryComponent);
    component = fixture.componentInstance;
  });

  fit(&#39;should show drop down if there is employee data&#39;, async ()  =&gt; {
    fixture.detectChanges();
    await fixture.whenStable();
  
    component.employee = {
      name: &#39;John Doe&#39;,
      age: 23,
      qualifications: [],
      address: {},
    }

    fixture.detectChanges();
    await fixture.whenStable();

    const hostElement: HTMLElement = fixture.nativeElement;
    const dropDown: HTMLInputElement = hostElement.querySelector(
      &#39;dropdown[name=&quot;employee&quot;]&#39;
    );

    expect(dropDown).toBeTruthy();
  } 
}

I am getting null for the dropdown element even when there is employee data set in the component.

How can I mock ReplaySubject to set employee data in the component so that it takes effect?

答案1

得分: 2

你需要为EmployeeService创建一个模拟对象,以便它包含你想要的数据。

这不是完整的答案,但希望能帮助你入门。

遵循带有!!标记的注释部分:

describe('SalaryComponent', () => {
  let component: SalaryComponent;
  let fixture: ComponentFixture<SalaryComponent>;
  // !! 声明一个用于 EmployeeService 的模拟对象
  let mockEmployeeService: jasmine.SpyObj<EmployeeService>;

  beforeEach(async () => {
    // !! 分配模拟对象
    mockEmployeeService = jasmine.createSpyObj<EmployeeService>('EmployeeService', {}, { currentEmployeeSub: of({ /* 在这里模拟 Employee 模型 */ } as any) });
    // !! 请查看 jasmine.createSpyObj,第一个字符串参数是可选的,用于调试目的。第二个参数可以是一个对象或一个数组,
    // 其中你可以模拟公共方法,第三个参数是一个对象,在这里你可以模拟公共实例变量,就像我们在这里做的一样。
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [],
      providers: [
        // !! 提供模拟对象
        { provide: EmployeeService, useValue: mockEmployeeService }, 
      ],
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(SalaryComponent);
    component = fixture.componentInstance;
  });

  fit('should show drop down if there is employee data', async ()  => {
    fixture.detectChanges();
    await fixture.whenStable();
  
    component.employee = {
      name: 'John Doe',
      age: 23,
      qualifications: [],
      address: {},
    }

    fixture.detectChanges();
    await fixture.whenStable();

    const hostElement: HTMLElement = fixture.nativeElement;
    const dropDown: HTMLInputElement = hostElement.querySelector(
      'dropdown[name="employee"]'
    );

    expect(dropDown).toBeTruthy();
  } 
}

请注意,上述代码是 TypeScript 代码,需要在合适的地方导入 EmployeeService 类型和其他相关模块。

英文:

You have to mock the EmployeeService for it to have the data you want.

This is not a complete answer but should hopefully get you started.

Follow comments with !!:

describe(&#39;SalaryComponent&#39;, () =&gt; {
  let component: SalaryComponent;
  let fixture: ComponentFixture&lt;SalaryComponent&gt;;
  // !! Declare a mock for EmployeeService
  let mockEmployeeService: jasmine.SpyObj&lt;EmployeeService&gt;;

  beforeEach(async () =&gt; {
    // !! Assign the mock
    mockEmployeeService = jasmine.createSpyObj&lt;EmployeeService&gt;(&#39;EmployeeService&#39;, {}, { currentEmployeeSub: of({ /* Mock Employee model here */ } as any) });
    // !! Look into jasmine.createSpyObj, the first string argument is optional 
    // and for debugging purposes. The 2nd argument can be an object or an array
    // where you can mock public methods and the 3rd argument is an object
    // where you can mock public instance variables as we did here.
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [],
      providers: [
        // !! Provide the mock
        { provide: EmployeeService, useValue: mockEmployeeService }, 
      ],
    })
      .compileComponents();
  });

  beforeEach(() =&gt; {
    fixture = TestBed.createComponent(SalaryComponent);
    component = fixture.componentInstance;
  });

  fit(&#39;should show drop down if there is employee data&#39;, async ()  =&gt; {
    fixture.detectChanges();
    await fixture.whenStable();
  
    component.employee = {
      name: &#39;John Doe&#39;,
      age: 23,
      qualifications: [],
      address: {},
    }

    fixture.detectChanges();
    await fixture.whenStable();

    const hostElement: HTMLElement = fixture.nativeElement;
    const dropDown: HTMLInputElement = hostElement.querySelector(
      &#39;dropdown[name=&quot;employee&quot;]&#39;
    );

    expect(dropDown).toBeTruthy();
  } 
}

huangapple
  • 本文由 发表于 2023年4月19日 19:17:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053868.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定