五、测试计算属性和观察者

计算属性和观察者是 Vue.js 组件逻辑的反应部分。它们的用途完全不同,即一个是同步的,另一个是异步的,这使得它们的行为略有不同。

在本章中,我们将经历测试它们的过程,我们将看到在测试过程中可以找到哪些不同的案例。

计算属性

计算属性是以另一种形式返回数据的简单反应函数。它们的行为与语言标准get/set属性完全相同:

class X {
  get fullName() {
    return `${this.name} ${this.surname}`;
  }
  set fullName(value) {
    // ...
  }
}

当您使用普通对象时,它将如下所示:

export default {
  computed: {
    fullName() {
      return `${this.name} ${this.surname}`;
    }
  }
};

您甚至可以按如下方式添加set属性:

export default {
  computed: {
    fullName: {
      get() {
        return `${this.name} ${this.surname}`;
      },
      set(value) {
        // ...
      }
    }
  }
};

测试计算属性

测试计算属性非常简单。有时,您可能不会专门测试计算属性,而是将其作为其他测试的一部分进行测试。然而,大多数时候,对它进行测试是很好的;无论计算属性是清理输入还是合并数据,我们都希望确保事情按预期进行。那么,让我们开始吧。

首先,创建一个Form.vue组件:

<template>
  <div>
    <form>
      <input type="text" v-model="inputValue">
      <span class="reversed">{{ reversedInput }}</span>
    </form>
  </div>
</template>
<script>
export default {
  props: ["reversed"],
  data: () => ({
    inputValue: ""
  }),
  computed: {
    reversedInput() {
      return this.reversed ?
        this.inputValue.split("").reverse().join("") :
        this.inputValue;
    }
  }
};
</script>

它将显示一个输入,在它旁边,您将看到相同的字符串,但相反。这只是一个愚蠢的例子,但足以证明这一点。

现在,将其添加到App.vue中,然后将其放在MessageList组件之后,记住导入它并将其包含在components组件选项中。然后,使用我们在其他测试中使用过的常见裸骨骼创建一个test/Form.test.js文件:

import { shallowMount } from "@vue/test-utils";
import Form from "../src/components/Form";
describe("Form.test.js", () => {
  let cmp;
  beforeEach(() => {
    cmp = shallowMount(Form);
  });
});

现在,创建一个包含两个测试用例的测试套件:

describe("Properties", () => {
  it("returns the string in normal order if reversed property is not true", () => {
    cmp.setData({ inputValue: "Yoo" });
    expect(cmp.vm.reversedInput).toBe("Yoo");
  });
  it("returns the reversed string if reversed property is true", () => {
    cmp.setData({ inputValue: "Yoo" });
    cmp.setProps({ reversed: true });
    expect(cmp.vm.reversedInput).toBe("ooY");
  });
});

我们可以访问cmp.vm中的组件实例,以便访问内部状态、计算属性和方法。然后,测试它只需要更改值,并确保当reversedfalse时返回相同的字符串。

对于第二种情况,几乎是相同的,唯一的区别是我们必须将reversed属性设置为true。我们可以通过cmp.vm...来改变它,但是vue-test-utils给了我们一个辅助方法setProps({ property: value, ... }),这使它变得非常简单。

就这样,;根据计算属性的不同,它可能需要更多的测试用例。

观察者

老实说,我还没有遇到过任何需要使用我的计算属性无法解决的观察程序的测试用例。我也看到它们被误用,导致组件之间的数据工作流程非常不清晰,并且把一切都搞乱了。所以,不要急于使用它们,要事先考虑。

正如您在Vue.js 文档中看到的那样 https://vuejs.org/v2/guide/computed.html#Watchers ),监视程序通常用于对数据更改做出反应并执行异步操作,例如执行 ajax 请求。

测试观察者

比方说,当来自状态的inputValue发生变化时,我们想做点什么。我们可以执行 ajax 请求,但由于这更复杂(我们将在下一课中更详细地介绍),所以我们只使用console.log function。在Form.vue组件选项中添加watch属性:

watch: {
  inputValue(newVal, oldVal) {
    if (newVal.trim().length && newVal !== oldVal) {
      console.log(newVal)
    }
  }
}

注意,inputValuewatch 函数与状态变量名匹配。按照惯例,Vue 将使用 watch 函数名在propertiesdata状态下查找它,在本例中为inputValue,由于它将在data中找到它,它将在那里添加 watcher。

请注意,watch 函数将新值作为第一个参数,旧值作为第二个参数。在本例中,我们选择仅在日志不为空且值不同时记录日志。通常,我们希望为每种情况编写一个测试,这取决于您的时间和代码的重要性。

那么,我们应该测试手表的哪些功能呢?好的,这是我们在下一课讨论测试方法时还将进一步讨论的问题,但假设我们只想知道它在应该调用console.log的时候调用它。因此,让我们在Form.test.js中添加 Watchers 测试套件的基本内容,如下所示:

describe("Form.test.js", () => {
  let cmp;
  describe("Watchers - inputValue", () => {
    let spy;
    beforeAll(() => {
      spy = jest.spyOn(console, "log");
    });
    afterEach(() => {
      spy.mockClear();
    });
    it("is not called if value is empty (trimmed)", () => {
      // TODO
    });
    it("is not called if values are the same", () => {
      // TODO
    });
    it("is called with the new value in other cases", () => {
      // TODO
    });
  });
});

在这里,我们在console.log方法上使用一个间谍,在开始任何测试之前初始化它,然后在每个测试之后重置它的状态,以便它们从一个干净的间谍开始。

要测试一个 watch 函数,我们只需要更改正在监视的内容的值,在本例中为inputValue状态。但是有一些奇怪的事情。。。让我们从上一个测试开始:

it("is called with the new value in other cases", () => {
  cmp.vm.inputValue = "foo";
  expect(spy).toBeCalled();
});

在这里,我们更改了inputValue,所以应该叫console.log间谍,对吗?不会的。但是等等,这有一个解释:与计算属性不同,观察者被推迟到 Vue 用于查找更改的下一个更新周期。所以,基本上,这里发生的事情是,console.log确实被调用,但是在测试完成之后。

请注意,我们正在通过访问vm属性以原始方式更改inputValue。如果我们想这样做,我们需要使用vm.$nextTickhttps://vuejs.org/v2/api/#vm-nextTick功能将代码延迟到下一个更新周期:

it("is called with the new value in other cases", done => {
  cmp.vm.inputValue = "foo";
  cmp.vm.$nextTick(() => {
    expect(spy).toBeCalled();
    done();
  });
});

注意这里我们调用了一个done函数,我们将其作为参数接收。那是单向玩笑https://jestjs.io/docs/en/asynchronous.html 可以测试异步代码。**

然而,有一种更好的方式*。vue-test-utils给我们的方法,如emittedsetData在引擎盖下处理。这意味着只需使用setData即可以更干净的方式编写最后一个测试:

it("is called with the new value in other cases", () => {
  cmp.setData({ inputValue: "foo" });
  expect(spy).toBeCalled();
});

我们也可以对下一个应用相同的策略,唯一的区别是间谍不应该被称为:

it("is not called if value is empty (trimmed)", () => {
  cmp.setData({ inputValue: "   " });
  expect(spy).not.toBeCalled();
});

最后,如果值相同,则测试不调用它稍微复杂一些。默认内部状态为空;因此,首先,我们需要更改它,等待下一个滴答声,然后清除 mock 以重置调用计数,然后再次更改它。然后,在第二个滴答声之后,我们可以检查间谍并完成测试。

如果我们在开始时重新创建组件,覆盖data属性,这将简单得多。记住,我们可以使用mountshallowMount函数的第二个参数覆盖任何组件选项:

it("is not called if values are the same", () => {
  cmp = shallowMount(Form, {
    data: () => ({ inputValue: "foo" })
  });
  cmp.setData({ inputValue: "foo" });
  expect(spy).not.toBeCalled();
});

收工

在本章中,您学习了如何测试 Vue 组件的部分逻辑:计算属性和观察者。我们可能会遇到两个不同的测试案例。您还了解了一些 Vue 内部构件,例如nextTick更新周期。

您可以在GitHub上找到本章的代码 https://github.com/alexjoverm/vue-testing-series/tree/Test-State-Computed-Properties-and-Methods-in-Vue-js-Components-with-Jest )。*