使用 :to 属性进行 VueRouter 单元测试

huangapple go评论53阅读模式
英文:

Unit Testing VueRouter with the :to Attribute

问题

I have a simple test case (Vue 2, Vuetify 2, vue-router, jest, vue-test-utils) where a Vuetify v-btn component is clicked and goes to a new route:

<v-btn id="create-plan-btn" :to="{ 'name': 'newPlan', query: { dept: 216001 }}">Create plan</v-btn>

However, I am unable to figure out how to register a click method that captures the :to property's action.

describe('when "Create Plan button is clicked", () => {
  const localVue = createLocalVue()
  let vuetify

  beforeEach(() => {
    vuetify = new Vuetify()
  })

  const $route = { path: '/student/index', name: 'studentLanding' }

  const mockRouter = {
    to: jest.fn(),
    push: jest.fn()
  }
  const wrapper = shallowMount(StudentLanding, {
    localVue,
    stubs: {'router-link': RouterLinkStub},
    mocks: {
      $route: $route,
      $router: mockRouter
    }
  })

  it('triggers Vue router call to the new plan page', async () => {
    const button = wrapper.find('#create-plan-btn')
    expect(button.exists()).toBe(true)
    expect(mockRouter.push).toHaveBeenCalledTimes(0)
    button.vm.$emit('click') // Vuetify buttons need "vm.$emit" to be triggered, not "trigger()" like a normal HTML button would
    await wrapper.vm.$nextTick()
    expect(mockRouter.push).toHaveBeenCalledTimes(1)
    expect(mockRouter.push).toHaveBeenCalledWith({ name: 'newPlan', query: { dept: 216001 }})
  })
})

Note that changing push to to doesn't make any difference in the above code either.

The test does succeed, however, if I change my <v-btn> to use a @click callback instead:

// Test succeeds with this code
<v-btn id="create-plan-btn" @click="redirect">Create plan</v-btn>
redirect () {
   this.$router.push({ name: 'newPlan', query: { dept: 216001 }});
}

Is there a way to modify this test that allows the :to prop to be captured on the <v-btn> click? I've tried to at least make sure the content of the :to prop is visible in the test, but I only ever get [object Object] sent back to me:

expect(button.attributes('to').toString()).toEqual({ name: 'newPlan', query: { dept: 216001 }})

Expected: {"name": "newPlan", "query": {"dept": 216001}}
Received: "[object Object]"


<details>
<summary>英文:</summary>

I have a simple test case (Vue 2, Vuetify 2, vue-router, jest, vue-test-utils) where a Vuetify v-btn component is clicked and goes to a new route

    &lt;v-btn id=&quot;create-plan-btn&quot; :to=&quot;{ &#39;name&#39;: &#39;newPlan&#39;, query: { dept: 216001 }}&quot;&gt;Create plan&lt;/v-btn&gt;

However, I am unable to figure out how to register a click method that captures the **:to** property&#39;s action. 

    describe(&#39;when &quot;Create Plan button is clicked&#39;, () =&gt; {
      const localVue = createLocalVue()
      let vuetify
    
      beforeEach(() =&gt; {
        vuetify = new Vuetify()
      })
    
      const $route = { path: &#39;/student/index&#39;, name: &#39;studentLanding&#39; }
    
      const mockRouter = {
        to: jest.fn(),
        push: jest.fn()
      }
      const wrapper = shallowMount(StudentLanding, {
        localVue,
        stubs: {&#39;router-link&#39;: RouterLinkStub},
        mocks: {
          $route: $route,
          $router: mockRouter
        }
      })
    
      it(&#39;triggers Vue router call to the new plan page&#39;, async () =&gt; {
        const button = wrapper.find(&#39;#create-plan-btn&#39;)
        expect(button.exists()).toBe(true)
        expect(mockRouter.push).toHaveBeenCalledTimes(0)
        button.vm.$emit(&#39;click&#39;) // Vuetify buttons need &quot;vm.$emit&quot; to be triggered, not &quot;trigger()&quot; like a normal HTML button would
        await wrapper.vm.$nextTick()
        expect(mockRouter.push).toHaveBeenCalledTimes(1)
        expect(mockRouter.push).toHaveBeenCalledWith({ name: &#39;newPlan&#39;, query: { dept: 216001 }})
      })
    })

&gt; ● when &quot;Create Plan button is clicked › triggers Vue router call to
&gt; the new plan page
&gt; 
&gt;     expect(jest.fn()).toHaveBeenCalledTimes(expected)
&gt; 
&gt;     Expected number of calls: 1
&gt;     Received number of calls: 0


Note that changing **push** to **to** doesn&#39;t make any difference in the above code either. 

The test **does** succeed, however, if I change my &lt;v-btn&gt; to use a @click callback instead: 

    // Test succeeds with this code
    &lt;v-btn id=&quot;create-plan-btn&quot; @click=&quot;redirect&quot;&gt;Create plan&lt;/v-btn&gt;

    redirect () {
       this.$router.push({ name: &#39;newPlan&#39;, query: { dept: 216001 }});
    }

Is there a way to modify this test that allows the :to prop to be captured on the &lt;v-btn&gt; click? I&#39;ve tried to at least make sure the content of the :to prop is visible in the test, but I only ever get **[object Object]** sent back to me: 

    expect(button.attributes(&#39;to&#39;).toString()).toEqual({ name: &#39;newPlan&#39;, query: { dept: 216001 }})

&gt; Expected: {&quot;name&quot;: &quot;newPlan&quot;, &quot;query&quot;: {&quot;dept&quot;: 216001}}
&gt;     Received: &quot;[object Object]&quot;




</details>


# 答案1
**得分**: 1

I'm fairly certain this has to do with your `emit` and not the fact that you're actually clicking it. 

I'd suggest changing your test a bit:

```javascript
it('triggers Vue router call to the new plan page', async () => {
    const button = wrapper.find('#create-plan-btn')
    expect(button.exists()).toBeTruthy()
    expect(mockRouter.push).toHaveBeenCalledTimes(0)
    await button.trigger('click')
    await wrapper.vm.$nextTick() // This could probably be omitted since we're awaiting the click above
    expect(mockRouter.push).toHaveBeenCalledTimes(1)
    expect(mockRouter.push).toHaveBeenCalledWith({ name: 'newPlan', query: { dept: 216001 }})
})

Also, what would be the purpose of testing that the button attributes are what they are? Since you've written them there and they're not dynamic in any way, then they will be that. However, if you really do want to test it, I think the problem is the toString() function, and you could instead try JSON stringifying it:

expect(JSON.stringify(button.attributes('to'))).toEqual({ name: 'newPlan', query: { dept: 216001 }})

Although it should also be possible to just do:

expect(button.attributes('to')).toStrictEqual({ name: 'newPlan', query: { dept: 216001 }})

I don't really know what you mean with your comment that Vuetify buttons would need to use $emit, I've always managed to test Vuetify buttons this way.

I'd also suggest one simple change, instead of using ID's for testing, create an attribute data-testid="router-button" that you name and find the component with:

const button = wrapper.find('[data-testid="router-button"]')

This makes it clear that it's used for testing and nothing else.

英文:

I'm fairly certain this has to do with your emit and not the fact that you're actually clicking it.

I'd suggest changing your test a bit:

it(&#39;triggers Vue router call to the new plan page&#39;, async () =&gt; {
    const button = wrapper.find(&#39;#create-plan-btn&#39;)
    expect(button.exists()).toBeTruthy()
    expect(mockRouter.push).toHaveBeenCalledTimes(0)
    await button.trigger(&#39;click&#39;)
    await wrapper.vm.$nextTick() // This could probably be omitted since we&#39;re awaiting the click above
    expect(mockRouter.push).toHaveBeenCalledTimes(1)
    expect(mockRouter.push).toHaveBeenCalledWith({ name: &#39;newPlan&#39;, query: { dept: 216001 }})
  })

Also, what would be the purpose of testing that the button attributes are what they are? Since you've written them there and they're not dynamic in any way, then they will be that. However, if you really do want to test it, I think the problem is the toString() function, and you could instead try JSON stringifying it:

expect(JSON.stringify(button.attributes(&#39;to&#39;))).toEqual({ name: &#39;newPlan&#39;, query: { dept: 216001 }})

Although it should also be possible to just do:

expect(button.attributes(&#39;to&#39;)).toStrictEqual({ name: &#39;newPlan&#39;, query: { dept: 216001 }})

I don't really know what you mean with your comment that Vuetify buttons would need to use $emit, I've always managed to test Vuetify buttons this way.

I'd also suggest one simple change, instead of using ID's for testing, create an attribute data-testid=&quot;router-button&quot; that you name and find the component with:

const button = wrapper.find(&#39;[data-testid=&quot;router-button&quot;]&#39;)

This makes it clear that it's used for testing and nothing else.

huangapple
  • 本文由 发表于 2023年2月7日 03:40:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365825.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定