Search code examples
angularjestjsangular-test

Issue with deep testing components in Angular and accessing html with debugElement


I have a hierarchy of components in my angular app which is as follows:

BookmarkContainer has the following child components:

  • BookmarksListComponent
  • CreateBookmarkComponent

BookmarksListComponent has a ngFor of BookmarkItemComponent

I am trying to access the template of BookmarkItemComponent from the test of the BookmarkContainer using DebugElement and its query() method.

Here is the code for BookmarkContainer:

@Component({
  selector: 'app-bookmarks-container',
  templateUrl: './bookmarks.container.html',
  styleUrls: ['./bookmarks.container.scss']
})
export class BookmarksContainerComponent implements OnInit {
  bookmarks$: Observable<Bookmark[]>;
  newBookmark: Bookmark;
  deletedBookmarkId: number;

  constructor(private bookmarkService: BookmarkService) {}

  ngOnInit(): void {
    this.setBookmarks();
  }

  private setBookmarks() {
    this.bookmarks$ = this.bookmarkService
      .currentUser()
      .pipe(
        mergeMap(session => this.bookmarkService.search({ account: session.user.uid }))
      );
  }
}

Here is the corresponding test:

describe('BookmarkContainerComponent', () => {
  let component: BookmarksContainerComponent;
  let fixture: ComponentFixture<BookmarksContainerComponent>;
  let httpTestingController: HttpTestingController;

  const aBookmark: Bookmark = {
    id: 42,
    title: 'a bookmark',
    url: 'http://www.example.com',
    account: '1'
  };
  const anotherBookmark: Bookmark = {
    id: 43,
    title: 'another bookmark',
    url: 'http://www.example.fr',
    account: '1'
  };

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, ReactiveFormsModule],
      declarations: [BookmarksContainerComponent, BookmarksListComponent, BookmarkItemComponent, CreateBookmarkComponent],
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(BookmarksContainerComponent);
    component = fixture.componentInstance;
    httpTestingController = TestBed.inject(HttpTestingController);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should search bookmarks', fakeAsync(() => {
    const sessionRequest = httpTestingController.expectOne('/api/system/connect');
    sessionRequest.flush({ user: { uid: '1' } });

    const bookmarksRequest = httpTestingController.expectOne('/api/bookmarks_link/search');
    bookmarksRequest.flush([aBookmark, anotherBookmark]);

    //Here I am not sure how to access the li(s) from the BookmarkItemComponent
    const item = fixture.debugElement.query(By.directive(BookmarkItemComponent));

});

I have tried to access the li(s) from the BookmarkItemComponent but without success: item is always null. I suspect it has something to do with the level of nesting of the BookmarkItemComponent... Can someone please help?

edit:

Template for BookmarkContainer:

<app-bookmarks-list
  [bookmarks]="bookmarks$ | async"
  [newBookmark]="newBookmark"
  [updatedBookmark]="updatedBookmark"
  [deletedBookmark]="deletedBookmarkId"
  (deleteBookmarkEvt)="confirmDeleteBookmark($event)"
  (updateBookmarkEvt)="openUpdateForm($event)">
</app-bookmarks-list>
...

Template for BookmarksListComponent:

<ng-container *ngFor="let bookmark of bookmarks">
  <div class="bookmark">
    <div class="">
      <ul class="bookmark-list">
        <app-bookmark-item
          [bookmark]="bookmark"
          (deleteBookmarkEvt)="deleteBookmark($event)"
          (updateBookmarkEvt)="updateBookmark($event)"
        >
        </app-bookmark-item>
      </ul>
    </div>
  </div>
</ng-container>

Template for BookmarkItemComponent:

<li class="bookmark-item bookmark-item-{{ bookmark.id }}">
  <span class="bookmark-icon"></span>
  <span class="bookmark-text">
    <a href="{{ bookmark.url }}" target="_blank" title="{{ 'BOOKMARKS.OPEN' | translate}}">
    {{bookmark.title}}
    </a>
  </span>
  <span class="bookmark-actions">
    <a title="{{ 'GLOBAL.UPDATE' | translate}}" (click)="updateBookmark(bookmark)">
      <i class="glyphicon glyphicon-pencil"></i>
    </a>
    <a title="{{ 'GLOBAL.DELETE' | translate}}" (click)="deleteBookmark(bookmark.id)">
      <i class="glyphicon glyphicon-remove "></i>
    </a>
  </span>
</li>

Solution

  • In order to see the http response data appear in the DOM, you need to execute a fixture.detectChanges() after your request.flush(...). Also, I would remove the fakeAsync wrap. It should not be needed.

    E.g.,

    it('should search bookmarks', function() {
        const sessionRequest = httpTestingController.expectOne('/api/system/connect');
        sessionRequest.flush({ user: { uid: '1' } });
    
        const bookmarksRequest = httpTestingController.expectOne('/api/bookmarks_link/search');
        bookmarksRequest.flush([aBookmark, anotherBookmark]);
    
        // add detectChanges here
        fixture.detectChanges();
    
        // item should be defined now.
        const item = fixture.debugElement.query(By.directive(BookmarkItemComponent));
    });