简体   繁体   English

如何使用 AngularFireDatabase 和 Jasmine Spy/Mock 测试 Angular 服务

[英]How to test an Angular Service using AngularFireDatabase with Jasmine Spy/Mock

I am trying to test my data service, and I can do it using the real services (AngularFireDatabase) but I am unable to get the mocked version working for testing.我正在尝试测试我的数据服务,我可以使用真实服务(AngularFireDatabase)来测试,但我无法让模拟版本用于测试。

My DataStorage class is used to combine local storage, remote storage and other functionality. My DataStorage class 用于结合本地存储、远程存储等功能。 This allows our application to easy change the service we integrate to.这使我们的应用程序可以轻松更改我们集成到的服务。

As such, we have some basic structures that are extended, such as the IDataRecord, which will always have a Key_ field as this is a requirement of our data model.因此,我们有一些扩展的基本结构,例如 IDataRecord,它总是有一个 Key_ 字段,因为这是我们的数据 model 的要求。

Part of DataStorage class:部分数据存储 class:

@Injectable()
export class DataStorage<T extends IDataRecord> {

    constructor(private AfDb_:AngularFireDatabase) { }

    public Exists(Id:string):Subject<boolean> {
        const Status$:Subject<boolean> = new Subject<boolean>();
        this.AfDb_.object<T>(`${Id}`).valueChanges()
            .subscribe( (OneRecord:T) => {
                if (OneRecord !== undefined && OneRecord !== null) {
                    if (OneRecord.Key_ !== undefined && OneRecord.Key_ !== null && OneRecord.Key_.length > 0) {
                        Status$.next(true);
                    } else {
                        Status$.next(false);
                    }
                } else {
                    Status$.next(false);
               }
            })
        ;
        return Status$;
    }

The following test snippet works with the real AngularFireDatabase.以下测试片段适用于真正的 AngularFireDatabase。

describe('DataStorage Service - Using AngularFire', () => {
    let ServiceUnderTest:DataStorage<IDataRecord>;
    let DependentService:AngularFireDatabase;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [
                AngularFireModule.initializeApp(environment.Firebase)
            ],
            providers: [
                AngularFireDatabase,
                DataStorage,
            ]
        });
        DependentService = TestBed.inject(AngularFireDatabase);
        ServiceUnderTest = TestBed.inject(DataStorage);
    });

    afterEach(() => {
        DependentService = null;
        ServiceUnderTest = null;
    });

    it('should be created', () => {
        expect(ServiceUnderTest).toBeTruthy('Service was created');
    });

    it('should confirm a record exists in storage', ( (done) => {
            const FileCheck$:Subject<boolean> = ServiceUnderTest.Exists('/Good');    // This exists in Firebase
        FileCheck$.subscribe( (Result:boolean) => {
            expect(Result).toBeTrue();
            done();
        });
    }));
});     

The real test passes, as the data exists in Firebase.实际测试通过,因为数据存在于 Firebase 中。

I am trying now to mock out AngularFire parts, so that I do not need the real connection for the testing to pass.我现在正在尝试模拟 AngularFire 部件,这样我就不需要真正的连接来通过测试。 I want to change the dependent service (AngularFire) so that this could be moved to another online service more easily if needed.我想更改依赖服务(AngularFire),以便在需要时可以更轻松地将其移动到另一个在线服务。

Failed test snipped:失败的测试被剪断:

// Create the fake record to be returned - Presence of Key_ with data means the record was found
const GoodDataRecord:ITestInterface = { Key_: 'Fake', Name: 'Fake Record' } ;
const TestData:Observable<ITestInterface> = of<ITestInterface>(GoodDataRecord);

const ValueChangesStub = {
    valueChanges: jasmine.createSpy('valueChanges').and.returnValue(TestData)
}
const AfDbObjectStub = {
    object: jasmine.createSpy('object').and.returnValue(ValueChangesStub)
}

describe('DataStorage Service - Mocked AngularFire', () => {
    let ServiceUnderTest:DataStorage<ITestInterface>;
    let DependentService:AngularFireDatabase;

    beforeEach( () => {
        TestBed.configureTestingModule({
            providers: [
                DataStorage,
                { provide: AngularFireDatabase, useValue: AfDbObjectStub }
            ]
        });
        // Inject both the service-to-test and its (spy) dependency
        DependentService = TestBed.inject(AngularFireDatabase);
        ServiceUnderTest = TestBed.inject(DataStorage);
    });

    it('should be created', () => {
        expect(ServiceUnderTest).toBeTruthy('Service was created');
    });

    it ('should return stubbed value from a spy', ( () => {
        let Status:boolean;
        const FileCheck$:Subject<boolean> = ServiceUnderTest.Exists('UsingStub');
        FileCheck$.subscribe( (Result:boolean) => {
            Status = Result;
        });
        expect(Status).toBeTrue();
    }));
});

This test always fails.这个测试总是失败。 Expected undefined to be true.

Adding a console.log() in the FileCheck$ subscribe block does not write out anything.在 FileCheck$ subscribe 块中添加 console.log() 不会写出任何内容。 This is not getting executed.这没有被执行。 However, if I add a console.log in the DataStorage class, I do see the fake data being returned.但是,如果我在 DataStorage class 中添加一个 console.log,我确实会看到返回的假数据。

    this.AfDb_.object<T>(`${BasePath}/${Id}`).valueChanges()
        .subscribe( (OneRecord:T) => {
            console.log(OneRecord.Key_);

This will show the 'Good' for the real test, as well as 'Fake' for the failed test.这将显示真实测试的“好”,以及失败测试的“假”。

As per @Alif50:根据@Alif50:

it ('should return stubbed value from a spy', ( done => {
    const FileCheck$:Subject<boolean> = ServiceUnderTest.Exists('Fake');
    FileCheck$.subscribe( (Result:boolean) => {
        expect(Result).toBeTrue();
        done();
    });
}));

This now introduces a different error: Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)这现在引入了一个不同的错误: Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)

Try changing the 2nd test to this:尝试将第二个测试更改为:

it ('should return stubbed value from a spy', done => {
        let Status:boolean;
        const FileCheck$:Subject<boolean> = ServiceUnderTest.Exists('UsingStub');
        FileCheck$.subscribe( (Result:boolean) => {
            Status = Result;
            expect(Status).toBeTrue();
            done(); // call done to let the test know you are done
        });
    });

The way you have it presented, the assertion ( expect(Status).toBeTrue() ) occurs before the subscribe block runs because .subscribe is asynchronous.按照您呈现的方式,断言( expect(Status).toBeTrue() )发生在subscribe块运行之前,因为.subscribe是异步的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM