如何在 jasmine 中编写简单的单元测试来测试我的 firestore 数据库数据是否获取数据并可以向其中写入数据?

发布于 2025-01-20 06:44:19 字数 6524 浏览 2 评论 0原文

我的网站是Angular构建的,它利用了Firestore数据库。对于这个问题,我将简化情况。我有一项名为Artist.Service的服务,该服务为艺术家处理所有CRUD操作。多个组件利用此Artist.Service。这就是服务的样子:

import { Injectable } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection,
  AngularFirestoreDocument,
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { Artist } from '../models/artist';

@Injectable({
  providedIn: 'root',
})
export class ArtistService {
  constructor(private db: AngularFirestore) {}

  create(artist: Observable<Artist>) {
    return this.db.collection('/artists').add(artist);
  }

  getAll(): any {
    return this.db.collection('/artists');
  }

  getOne(artistId: string): AngularFirestoreDocument<Artist> {
    return this.db.doc('/artists/' + artistId);
  }

  update(artistId: string, artist: Observable<Artist>) {
    return this.db.doc('/artists/' + artistId).update(artist);
  }

  delete(artistId: string) {
    return this.db.doc('/artists/' + artistId).delete();
  }
}

我想测试是否能够在不同情况下从每种方法中获取这些数据。这就是为什么我想在每个组件规格文件(用于测试的文件)中进行测试。

以下代码适用于Admin-Artist组件。这是艺术家在桌子中展示的地方。

import {
  AfterViewInit,
  Component,
  OnDestroy,
  OnInit,
  ViewChild,
} from '@angular/core';
import { AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs/internal/Observable';
import { Subscription } from 'rxjs/internal/Subscription';
import { ArtistService } from '../../service/artist.service';

import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort, Sort } from '@angular/material/sort';
import { Artist } from 'src/app/models/artist';

@Component({
  selector: 'admin-artist',
  templateUrl: './admin-artist.component.html',
  styleUrls: ['./admin-artist.component.css'],
})
export class AdminArtistComponent implements OnInit, OnDestroy {
  artists: Artist[];
  filteredArtists: Artist[];
  subscription: Subscription;

  displayedColumns: string[] = ['img', 'name', 'country', 'edit'];
  dataSource: MatTableDataSource<Artist>;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  constructor(private artistService: ArtistService) {}
  ngOnInit() {
    this.subscription = this.artistService
      .getAll()
      .valueChanges({
        idField: 'key',
      })
      .subscribe((artists: any) => {
        this.filteredArtists = this.artists = artists.map((artist: any) => {
          return {
            name: artist['name'],
            country: artist['country'],
            imageUrl: artist['imageUrl'],
            key: artist['key'],
          };
        });
        this.dataSource = new MatTableDataSource<Artist>(this.filteredArtists);
        this.initPaginatorAndSort();
      });
  }

  initPaginatorAndSort(): void {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

  filter(query: string) {
    this.filteredArtists = query
      ? this.artists.filter(
          (p) =>
            p.name.toLowerCase().includes(query.toLowerCase()) ||
            p.country.toLowerCase().includes(query.toLowerCase())
        )
      : this.artists;
    this.dataSource = new MatTableDataSource<Artist>(this.filteredArtists);
    this.initPaginatorAndSort();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

这是该表单的代码,该表格可以处理保存,更新和删除。该文件称为Artist-form

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, take } from 'rxjs';
import { Artist } from 'src/app/models/artist';
import { ArtistService } from '../../service/artist.service';

@Component({
  selector: 'artist-form',
  templateUrl: './artist-form.component.html',
  styleUrls: ['./artist-form.component.css'],
})
export class ArtistFormComponent implements OnInit {
  artist: any = {};
  id: string;

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private artistService: ArtistService
  ) {}

  save(artist: Observable<Artist>) {
    if (this.id) {
      this.artistService.update(this.id, artist);
    } else {
      this.artistService.create(artist);
    }
    this.router.navigate(['/admin/artists']);
  }

  delete() {
    if (!confirm('Are you sure you want to delete this artist?')) return;
    this.artistService.delete(this.id);
    this.router.navigate(['/admin/artists']);
  }
  ngOnInit() {
    this.id = this.route.snapshot.paramMap.get('id');
    if (this.id) {
      this.artistService
        .getOne(this.id)
        .valueChanges({
          idField: 'key',
        })
        .pipe(take(1))
        .subscribe((p) => (this.artist = p));
    }
  }
}

所有这些代码都可以使用。当我想使用茉莉和业力测试它时,问题就来了。我想进行简单的单元测试,以确保每个函数都起作用。我从Getall方法开始,但很快就意识到该测试总是会随着成功而回来。这是我在Admin-Artist.spec文件中写的测试:

import {
  ComponentFixture,
  inject,
  TestBed,
  tick,
  waitForAsync,
} from '@angular/core/testing';

import { AdminArtistComponent } from './admin-artist.component';
import { ArtistService } from '../../service/artist.service';
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from 'src/environments/environment';
import { of } from 'rxjs/internal/observable/of';

describe('AdminArtistComponent', () => {
  let component: AdminArtistComponent;
  let fixture: ComponentFixture<AdminArtistComponent>;
  let service: ArtistService;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      declarations: [AdminArtistComponent],
      providers: [ArtistService],
      imports: [
        AngularFireModule.initializeApp(environment.firebase),
        AngularFireAuthModule,
        AngularFirestoreModule,
      ],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AdminArtistComponent);
    service = TestBed.inject(ArtistService);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should get all the music', waitForAsync(
    inject([ArtistService], (service: ArtistService) => {
      expect(service.getAll()).not.toBeUndefined;
    })
  ));
});

有人可以帮助我吗?我只需要为每种方法进行简单的单元测试,但是我不知道从哪里开始。 如果这个问题需要进一步的解释,请告诉我。提前致谢

My website is build in Angular and it makes use of a firestore database. For this question I will simplify the situation. I have a service, called the artist.service, which handles all the crud operations for an artist. Multiple components make use of this artist.service. This is what the service looks like:

import { Injectable } from '@angular/core';
import {
  AngularFirestore,
  AngularFirestoreCollection,
  AngularFirestoreDocument,
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { Artist } from '../models/artist';

@Injectable({
  providedIn: 'root',
})
export class ArtistService {
  constructor(private db: AngularFirestore) {}

  create(artist: Observable<Artist>) {
    return this.db.collection('/artists').add(artist);
  }

  getAll(): any {
    return this.db.collection('/artists');
  }

  getOne(artistId: string): AngularFirestoreDocument<Artist> {
    return this.db.doc('/artists/' + artistId);
  }

  update(artistId: string, artist: Observable<Artist>) {
    return this.db.doc('/artists/' + artistId).update(artist);
  }

  delete(artistId: string) {
    return this.db.doc('/artists/' + artistId).delete();
  }
}

I want to test if I am able to get data from each of these methods in different scenario's. This is why I want to make the tests in each of the components spec files (files used for testing).

The following code is for the admin-artist component. This is where artists get displayed in a table.

import {
  AfterViewInit,
  Component,
  OnDestroy,
  OnInit,
  ViewChild,
} from '@angular/core';
import { AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs/internal/Observable';
import { Subscription } from 'rxjs/internal/Subscription';
import { ArtistService } from '../../service/artist.service';

import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort, Sort } from '@angular/material/sort';
import { Artist } from 'src/app/models/artist';

@Component({
  selector: 'admin-artist',
  templateUrl: './admin-artist.component.html',
  styleUrls: ['./admin-artist.component.css'],
})
export class AdminArtistComponent implements OnInit, OnDestroy {
  artists: Artist[];
  filteredArtists: Artist[];
  subscription: Subscription;

  displayedColumns: string[] = ['img', 'name', 'country', 'edit'];
  dataSource: MatTableDataSource<Artist>;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  constructor(private artistService: ArtistService) {}
  ngOnInit() {
    this.subscription = this.artistService
      .getAll()
      .valueChanges({
        idField: 'key',
      })
      .subscribe((artists: any) => {
        this.filteredArtists = this.artists = artists.map((artist: any) => {
          return {
            name: artist['name'],
            country: artist['country'],
            imageUrl: artist['imageUrl'],
            key: artist['key'],
          };
        });
        this.dataSource = new MatTableDataSource<Artist>(this.filteredArtists);
        this.initPaginatorAndSort();
      });
  }

  initPaginatorAndSort(): void {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

  filter(query: string) {
    this.filteredArtists = query
      ? this.artists.filter(
          (p) =>
            p.name.toLowerCase().includes(query.toLowerCase()) ||
            p.country.toLowerCase().includes(query.toLowerCase())
        )
      : this.artists;
    this.dataSource = new MatTableDataSource<Artist>(this.filteredArtists);
    this.initPaginatorAndSort();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

This is the code for the form, which handles saving, updating and deleting. The file is called artist-form:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, take } from 'rxjs';
import { Artist } from 'src/app/models/artist';
import { ArtistService } from '../../service/artist.service';

@Component({
  selector: 'artist-form',
  templateUrl: './artist-form.component.html',
  styleUrls: ['./artist-form.component.css'],
})
export class ArtistFormComponent implements OnInit {
  artist: any = {};
  id: string;

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private artistService: ArtistService
  ) {}

  save(artist: Observable<Artist>) {
    if (this.id) {
      this.artistService.update(this.id, artist);
    } else {
      this.artistService.create(artist);
    }
    this.router.navigate(['/admin/artists']);
  }

  delete() {
    if (!confirm('Are you sure you want to delete this artist?')) return;
    this.artistService.delete(this.id);
    this.router.navigate(['/admin/artists']);
  }
  ngOnInit() {
    this.id = this.route.snapshot.paramMap.get('id');
    if (this.id) {
      this.artistService
        .getOne(this.id)
        .valueChanges({
          idField: 'key',
        })
        .pipe(take(1))
        .subscribe((p) => (this.artist = p));
    }
  }
}

All of this code works. The issue comes when I want to test it using jasmine and karma. I want to make simple unit tests that make sure that each function works. I started with the getAll method, but quickly realized that the test always came back as SUCCEEDED. This is the test i wrote in the admin-artist.spec file:

import {
  ComponentFixture,
  inject,
  TestBed,
  tick,
  waitForAsync,
} from '@angular/core/testing';

import { AdminArtistComponent } from './admin-artist.component';
import { ArtistService } from '../../service/artist.service';
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from 'src/environments/environment';
import { of } from 'rxjs/internal/observable/of';

describe('AdminArtistComponent', () => {
  let component: AdminArtistComponent;
  let fixture: ComponentFixture<AdminArtistComponent>;
  let service: ArtistService;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      declarations: [AdminArtistComponent],
      providers: [ArtistService],
      imports: [
        AngularFireModule.initializeApp(environment.firebase),
        AngularFireAuthModule,
        AngularFirestoreModule,
      ],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AdminArtistComponent);
    service = TestBed.inject(ArtistService);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should get all the music', waitForAsync(
    inject([ArtistService], (service: ArtistService) => {
      expect(service.getAll()).not.toBeUndefined;
    })
  ));
});

Can anyone help me with this? I just need a simple unit test for every method, but I dont know where to start.
If this question needs further explanation, let me know. Thanks in advance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

若水般的淡然安静女子 2025-01-27 06:44:19

为此,您应该编写一个不会进行任何 API 调用的模拟服务、虚拟值或间谍,因为这不是一个好的做法。

您可以在 Angular 文档 中阅读精彩的解释,其中包含针对您的特定情况的示例需要。

For this purpose, you should either write a mock service, dummy value, or spy that will not make any API calls because it's not a good practice.

You can read a great explanation in Angular documentation with examples for your specific need.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文