使用Jest测试时如何修复未正确检查的AJV模式

时间:2019-05-10 09:56:38

标签: javascript jestjs ajv

基本上,我目前正在为一个使用AJV架构检查json文件是否有效的函数编写单元测试。问题在于,对模式的检查可以在浏览器中进行,而不能在测试中进行。

InvalidFileError

export class InvalidFileError extends Error {
    constructor(message) {
        super(message)
        this.name = "InvalidFileError"
    }
}

我要测试的功能

export function importFile(e, importScenarios, importDevices) {
    const file = e.target.files[0]
    const fileReader = new FileReader()
    fileReader.readAsText(file)

    fileReader.onload = () => { // File loaded
        const fileContent = JSON.parse(fileReader.result)

        const ajv = new Ajv({allErrors: true})
        const validate = ajv.compile(schema)
        const contentIsValid = validate(fileContent)

        console.log("Content is valid: ", contentIsValid)
        if (contentIsValid) {
            importScenarios(fileContent.scenarios)
            importDevices(fileContent.devices)
        } else {
            throw new InvalidFileError("This file doesn't match the schema")
        }
    }
}

我编写的当前测试

describe("Does Importing a file work properly?", () => {
    let file
    let e = {
        target: {
            files: []
        }
    }

    let importScenarios = () => {}
    let importDevices = () => {}

    test("Does it work with a file matching the schema?", () => {
        file = new Blob(correctTestContent, { type: "application/json" })

        e.target.files.push(file)
        expect(() => {
            FileManager.importFile(e, importScenarios, importDevices)
        }).not.toThrow(InvalidFileError)
    })

    test("Does it work with a file not matching the schema??", () => {
        file = new Blob(incorrectTestContent, { type: "application/json" })
        e.target.files.push(file)

        expect(() => {
            FileManager.importFile(e, importScenarios, importDevices)
        }).toThrow(InvalidFileError)
    })

    afterEach(() => {
        e.target.files = []
    })
})

当我在浏览器中使用此功能时,通过上传无效文件会引发错误,如果我上传有效文件则不会。 在测试中,这应该是完全相同的,但不幸的是,不是。

1 个答案:

答案 0 :(得分:0)

问题是您要测试的代码是异步的,而您编写的测试却不是。

运行测试时,在执行相应测试期间,不会执行onload的{​​{1}}回调。而是在执行测试后调用它。实际上,因为您有以下声明:

FileReader

console.log("Content is valid: ", contentIsValid) 方法中,您应该在控制台中看到类似以下的消息:

  

测试完成后无法记录。您是否忘了等待测试中的异步操作?

您需要进行测试asynchronous,以便它们等待importFile回调执行。不幸的是,您的代码很难按原样进行测试,因为您无法知道何时执行onload回调,因此也很难等到那一刻。

解决此问题的一种方法是将异步代码包装在Promise中并返回它,以便我们可以等到诺言完成为止。使用这种方法,您的onload看起来像:

importFile

然后,您可以通过在测试中返回Promise来测试此方法(以便开玩笑地知道它必须等待,直到诺言被解决或被拒绝):

export function importFile(e, importScenarios, importDevices) {
    const file = e.target.files[0]
    const fileReader = new FileReader()
    fileReader.readAsText(file)

    return new Promise((resolve, reject) => {
        fileReader.onload = () => { // File loaded
            const fileContent = JSON.parse(fileReader.result)

            const ajv = new Ajv({allErrors: true})
            const validate = ajv.compile(schema)
            const contentIsValid = validate(fileContent)

            if (contentIsValid) {
                importScenarios(fileContent.scenarios)
                importDevices(fileContent.devices)
                resolve()
            } else {
                reject(new InvalidFileError("This file doesn't match the schema"))
            }
        }
    });
}

请注意,我已经重新定义了变量let importScenarios = jest.fn() let importDevices = jest.fn() test("Does it work with a file matching the schema?", () => { expect.assertions(2); file = new Blob(correctTestContent, { type: "application/json" }) e.target.files.push(file) return FileManager.importFile(e, importScenarios, importDevices).then(() => { expect(importScenarios).toHaveBeenCalledTimes(1); expect(importDevices).toHaveBeenCalledTimes(1); }); }); test('Does it work with a file not matching the schema??', () => { expect.assertions(1); file = new Blob(incorrectTestContent, { type: "application/json" }) e.target.files.push(file) return FileManager.importFile(e, importScenarios, importDevices).catch((e) => { expect(e).toBeInstanceOf(InvalidFileError); }); }); importScenarios,以便它们是mock functions,我们可以检查它们是否被调用。另外,请注意使用importDevices来验证是否调用了一定数量的断言。

最后,请考虑到,如果重新定义expect.assertions以便返回承诺,则可能必须更改调用它的位置以处理拒绝情况。您在哪里:

importFile

您将需要:

try {
    FileManager.importFile(e, importScenarios, importDevices)
} catch(e) {
    // Some treatment of your exception
}