如何在角度单元测试中使用 debugeElement 访问 nativeElement 属性

Posted

技术标签:

【中文标题】如何在角度单元测试中使用 debugeElement 访问 nativeElement 属性【英文标题】:How to access nativeElement attribute using debugeElement in angular unit test 【发布时间】:2018-09-29 19:02:13 【问题描述】:

我想编写一个简单的单元测试来检查当某个值为空或为空时按钮是否被禁用。

以下是我的规范文件:

test.spec.ts

import  async, ComponentFixture, TestBed  from '@angular/core/testing';
import 'reflect-metadata';
import  Component,
         DebugElement,
         ViewChild 
         from  "@angular/core";

import By from "@angular/platform-browser";
import  FormsModule  from '@angular/forms';
import  HttpClientModule, HttpClient  from '@angular/common/http';

import  ListCommentsComponent  from './list-comments.component';
import CommentsDataService from '../../services/comments/comments-data.service'








describe('ListCommentsComponent', () => 



  let component: ListCommentsComponent;
  let fixture: ComponentFixture<ListCommentsComponent>;
  let debugElement:DebugElement;
  let htmlElement:HTMLElement;
  let addCommentBtn:DebugElement;


  beforeEach(async(() => 
    TestBed.configureTestingModule(

      imports: [ FormsModule, HttpClientModule],

      declarations: [ ListCommentsComponent ],

      providers:[provide: CommentsDataService],
     // providers:[HttpClientModule, HttpClient]
    )
    .compileComponents();
  ));

  beforeEach(() => 
    fixture = TestBed.createComponent(ListCommentsComponent);
    component = fixture.componentInstance;

    fixture.detectChanges();


  );



  fit('should  be disabled when comment is empty', () => 

     component.comment = '';
addCommentBtn = fixture.debugElement.query(By.css('button.addCommentBtn'));



expect(addCommentBtn.nativeElement.getAttribute('disabled')).toBeTruthy();

  );


);

测试失败:

预期未定义为“禁用”。

我在网上搜索了 debugElement 函数来获取元素的属性,但找不到确切的解决方案。

这是一个类似SO Question的参考

更新:

下面是组件的 HTML

HTML

       <div class="container">
<!--<h1 id="hello"></h1>-->
    <div class="row listCommentsContainer">
        <div class="col-lg-8 col-sm-8" *ngFor="let commentData of commentsData| async ; let i = index">
          <ol style="list-style: none;">
          <li class="listComments">

            <div  style="display: block">
            <div style="display:inline-block;">
              <a class="avatar">

                <img style="" src="commentData.profile_image">
              </a>
            </div>
            <a class="commentPostByUserName">
              <span class="commentPostByUserName" style="">commentData.first_name</span>
            </a>
              <div class="commentTime">commentData.time_stamp</div>
            </div>
            <div class="commentTextDisplay">commentData.object</div>

            <br>

            <!-- Reply Link -->
            <div class="addReplyContainer" >
              <a  class="addReplyLink"  (click)="showReplyTextArea($event,i )">Reply</a>
            </div>

            <!-- Add Reply -->
            <div [attr.commentId] = "commentData.target_id" *ngIf = "selectedindex == i "
                 class="addReplyContainer replyTextAreaContainer" >
              <textarea [(ngModel)]="reply" style="width:100%"
                        class="replyText commentText addReplyTextarea form-control"></textarea>
              <button [attr.commentId]="commentData.id" [disabled] = "!reply || reply.length>500" class="btn btn-success addCommentBtn" (click)="addReply(commentData.target_id)">Add</button>
            </div>
            <!-- -----Add Reply end------ -->

            <!-- List Replies -->
            <div class="replies col-lg-8 col-sm-8" *ngFor="let reply of commentData.comment">
              <ol style="list-style: none;">
                <li class="listComments listReplies">

                  <div  style="display: block">
                    <div style="display:inline-block;">
                      <a class="avatar">

                        <img style="" src="reply.profile_image">
                      </a>
                    </div>
                    <a class="commentPostByUserName">
                      <span class="commentPostByUserName" style="">reply.first_name</span>
                    </a>

                  </div>
                  <div class="commentTextDisplay replyTextDisplay">reply.object</div>
                </li>
              </ol>
            </div>
            <!-- -------list reply end-------- -->

          </li>
          </ol>
        </div>

    </div>

    <!-- Add Comment-->
    <div class="row">
      <div  class="addCommentContainer col-lg-6 col-sm-12">

          <textarea maxlength="500"
                    [(ngModel)]="comment" class="commentText form-control"
                    placeholder="Add Comment">
          </textarea>

        <button #addCommentBtn [disabled] = "!comment || comment.length>500" (click)="addComment()" class="btn addCommentBtn btn-success">Add</button>

      </div>
    </div>
  <!-- Add Comment end-->
</div>

在 html 的末尾有 textarea 和 button,我正在测试当 textarea 为空时将其禁用。

在组件类中,变量名是注释,如果按钮被禁用,注释应该是空的。 这是我测试的断言。

下面是组件类:

import Component, OnInit, ElementRef, ViewChild, Renderer2 from '@angular/core';
import  CommentsDataService from "../../services/comments/comments-data.service";
import  Observable  from 'rxjs/Observable';
import  mergeMap  from 'rxjs/operators';
import IComment from "../../comments";
import ICommentType from "../../commentType";



declare let jQuery: any;
@Component(
  selector: 'app-list-comments',
  templateUrl: './list-comments.component.html',
  styleUrls: ['./list-comments.component.css'],
  providers:[CommentsDataService]

)
export class ListCommentsComponent implements OnInit 

  constructor(private commentsDataService:CommentsDataService, private el: ElementRef) 
  

  commentsData :any; // Comment Data received from service.
  comment:string; // Comment text; declaration.
  reply:string;
  commentObj:any;
  commentArrayObj:any;
  public selectedindex;
  getComments;  // Declaration of function.
  saveComment;
  getUser;




  /**
   * This Function is triggered to
   * call save function and append
   * new comment.
   * Append new comment
   *
   */
  addComment()
  

    this.comment = this.comment.trim();
    let commentObj;
    let comment = this.comment;
    commentObj =
      "root_id":"1",
      "target_id": "1",
      "object": this.comment,
      "time_stamp":'2 secs ago',
      "profile_image":"/../../assets/images/no-user.png",
      "first_name" : "john",
    ;

    //let commentObj = this.commentObj; //Put this.commentObj to push it into this.commentData

    if( typeof this.comment == "undefined" || this.comment === '' || this.comment == null ||  this.comment == '\n' || this.comment.length>500 )
    

      return false;
    
    else
    
      this.commentsData.push( commentObj );
      this.comment = ''; // Empty comment Textarea.
      return comment;

    

  

  /**
   *
   * Function triggered when
   * reply link is clicked
   * @param event
   * @param index
   */
  showReplyTextArea(event, index);
  showReplyTextArea(event, index)
  


    this.selectedindex = index;
    console.log( this.selectedindex);

  

  /**
   * Append Reply to list.
   * @param event
   * @param target_id
   */
  addReply(target_id)
  
    let commentData = this.commentsData; //creating local variable
    let reply = this.reply; //creating local variable


    if(reply == "" || reply === '/n' || reply.length <=0 ||   reply.length > 500 )
    
      return false;
    
    else
    


      this.commentObj = 
        root_id:1,
        target_id: target_id,
        object: reply,
        profile_image:"/../../assets/images/no-user.png",
        actor:1,
        first_name : "john"
      ;

      let  commentObj = this.commentObj;

      this.commentArrayObj =[
        
          root_id:1,
          target_id: target_id,
          object: reply,
          actor:"user:123",
          time_stamp: "2 min ago",
          first_name : "john",
          profile_image:"/../../assets/images/no-user.png"
        
      ];


      let commentArrayObj1 = this.commentArrayObj;

      console.log('commentObj');
      console.log(commentObj);
      jQuery.each(this.commentsData, function (index, value) 

        if(value.target_id == target_id)
        

          if(! ('comment' in commentData[index]))
          
            commentData[index]['comment'] = commentArrayObj1;
          
          else
          
            commentData[index].comment.push(commentObj);
          


        
      )
    

    this.reply = ''; // Empty textarea after posting reply.

  

  ngOnInit() 


    this.commentsData =  this.commentsDataService.getComments();
    //Service call for Comments listing.
/*    this.commentsDataService.getComments().subscribe(comments=>
        this.commentsData = comments
      
    )*/



  


【问题讨论】:

请用您正在测试的组件更新问题 @TomaszKula 完成。 【参考方案1】:

您需要在设置component.comment = '' 后更新视图。 在查询您的元素并做出断言之前添加 fixture.detectChanges() 调用。

fit('should  be disabled when comment is empty', () => 
      component.comment = '';

      fixture.detectChanges() // <-- add this to update the view

      addCommentBtn = fixture.debugElement.query(By.css('button.addCommentBtn'));

      // this asserts that button matches <button disabled>Hello</button>    
      expect(addCommentBtn.nativeElement.getAttribute('disabled')).toEqual('');

      // this asserts that button matches <button>Hello</button>          
      expect(addCommentBtn.nativeElement.getAttribute('disabled')).toEqual(null);
    
);

值得注意的是,您可以在不访问nativeElement 的情况下检查DebugElement

【讨论】:

Tomasz,我已经在 beforeach 块中添加了这个。感谢您的答复。但这种改变没有效果。仍然让 Expected '' 被'禁用'。 将其添加到 before 块中为时过早。将评论设置为'' 后,您必须更新视图。除非这是默认值。 是的,我添加了它,但仍然出现错误。 " 预期 ' ' 会被'禁用'" 如果我检查任何其他属性(如 id)的测试,它通过了。 太棒了,感谢您的时间和分享知识。干杯!!【参考方案2】:

这里是对代码的轻微修改。试试这个

expect(addCommentBtn.nativeElement.attributes).toContain['disabled'];

【讨论】:

debugElement.properties 在我的单元测试用例中为空,有什么解决方案吗?示例 html 代码是 "" 并且在单元测试中 const comp = fixture.debugElement.query(By.css('con-entity-form-content' ));期望(comp.properties.entity).toBe(component.company);但 comp.properties 是 。为什么?【参考方案3】:

另一个对我有用的细微变化

   const disabledEl = fixture.debugElement.query(By.css('.disabled-el'));
   expect(Object.keys(disabledEl.attributes)).toContain('disabled');

不确定这是否与我的 angular 版本或 jasmine/karma 有关,但它有效。

【讨论】:

以上是关于如何在角度单元测试中使用 debugeElement 访问 nativeElement 属性的主要内容,如果未能解决你的问题,请参考以下文章

如何订阅角度服务单元测试中的错误案例

用玩笑进行单元测试时,如何以角度模拟 ResizeObserver polyfill?

如何在角应用程序中单元测试router.navigate

带有自定义类型的角度单元测试给出了找不到命名空间

麻烦单元测试角度的反应性表单字段

使用 jasmine 对控制器中基于资源的工厂进行角度 js 单元测试