Skip to content

shallowMount: Stub child component props (explicit defined in componente or from mixin) #987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rodrigopedra opened this issue Oct 2, 2018 · 6 comments

Comments

@rodrigopedra
Copy link

What problem does this feature solve?

I faced this today when trying to test if a child component received a prop.

Unfortunately the child component defines this specific prop through a mixin, so using shallowMount doesn't let me assert using the props() helper:

// <div><ChildComponent :foo="bar" /></div>
const wrapper = shallowMount(ParentComponent);
const expectedPropValue = {id: 1};

// just for clearness
expect(wrapper.vm.bar).toEqual(expectedPropValue);

// this assertion fails
expect(wrapper.find(ChildComponent).props('foo')).toEqual(expectedPropValue);

As the props() method returns an empty object, the assertion fails.

For now I am testing like this

const wrapper = shallowMount(ParentComponent);

expect(wrapper.find(ChildComponent).attributes('foo')).toBeDefined();

Note I cannot test the attribute value as shallowMount converts all attributes to string, so the value of the foo attribute becomes '[Object object]'

Reading through #814, #814 and #965 it seems that when stubbing a component that doesn't have a property explicitly defined in the component, vue -test-utils doesn't count it as a prop.

The ideal was to the the stubbed component to have all properties, but I understand it can be hard to implement.

My proposal is to either add all attributes passed using v-bind on the template as props to a stubbed child component so we can test the value passed.

Or keep them as attributes but have their values formatted using JSON.stringify when using v-bind.

What does the proposed API look like?

// <div><ChildComponent :foo="bar" title="child" /></div>
const wrapper = shallowMount(ParentComponent);
const expectedPropValue = {id: 1};

expect(wrapper.vm.bar).toEqual(expectedPropValue);

// foo was passed using v-bind, to it is added to props
expect(wrapper.find(ChildComponent).props('foo')).toEqual(expectedPropValue);

// title is passed as a regular string, so it is kept as an attribute
expect(wrapper.find(ChildComponent).attributes('title')).toBe('child');

// another option is to keep foo in attributes, but to have its value encoded with JSON.stringify
expect(wrapper.find(ChildComponent).attributes('foo')).toBe(JSON.stringify(expectedPropValue));
@rodrigopedra
Copy link
Author

rodrigopedra commented Oct 2, 2018

Maybe this is a better api:

const wrapper = shallowMount(ParentComponent, {
  stubs: {
    'ChildComponent': {
      // mixins: [ ... ],
      props: [ 'foo' ],
    },
  },
});

This way shallowMount could know about any additional props (or methods, mixins, etc.) the ChildComponent needs

@rodrigopedra
Copy link
Author

rodrigopedra commented Oct 2, 2018

I ended stubbing the component locally:

const ChildComponentStub = {
  name: 'child-component',
  template: '<div />',
  props: [ 'foo' ],
};

describe('test ParentComponent', () => {
  it('renders as expected', () => {
    const wrapper = shallowMount(ParentComponent, {
      stubs: {ChildComponent: ChildComponentStub}
    });

    expect(wrapper.find(ChildComponentStub).props('foo')).toBe(wrapper.vm.bar);
  });
});

It works in the way I can test the value passed to the prop, but it won't catch if the ChildComponent changes its props names.

@rodrigopedra
Copy link
Author

So after thinking about it I wrote this helper function:

// ./tests/helpers/create-stub.js
const extractProps = function extractProps(component, props) {
  const results = Object.assign({}, props);

  if (Array.isArray(component.mixins)) {
    const reduced = component.mixins
      .reduce((result, mixin) => Object.assign(result, extractProps(mixin, {})), {});

    Object.assign(results, reduced);
  }

  if (component.props) {
    if (Array.isArray(component.props)) {
      const reduced = component.props
        .reduce((result, prop) => Object.assign(result, {[prop]: {}}), {});

      Object.assign(results, reduced);
    } else {
      Object.assign(results, component.props);
    }
  }

  return results;
};

export default function createStub(name, component) {
  return {
    name,
    props: extractProps(component, {}),
    template: '<div />',
  };
}

I don't know if this is the best approach, and it would be useful to have something similar bundled in the vue-test-utils itself.

If this is something desirable, I can try to send a PR, although it would be a fisrt-time contribution to the repo.

@rodrigopedra rodrigopedra changed the title Pass any props using v-bind to a stubbed child component (shallowMount) shallowMount: Stub child component props (explicit defined in componente or from mixin) Oct 3, 2018
@rodrigopedra rodrigopedra changed the title shallowMount: Stub child component props (explicit defined in componente or from mixin) shallowMount: Stub child component props (explicit defined in componente or from mixin) Oct 3, 2018
@rodrigopedra rodrigopedra changed the title shallowMount: Stub child component props (explicit defined in componente or from mixin) shallowMount: Stub child component props (explicit defined in componente or from mixin) Oct 3, 2018
@robokozo
Copy link

robokozo commented Oct 22, 2018

I've done something similar not too long ago that, surprisingly, seems to work pretty great.

<div class="dashboard-wrapper">
    .........
    <ActivityDeleteModal class="dashboard-wrapper__modal" v-model="deleteModalOpen">
    </ActivityDeleteModal>
</div>

My test looks like this:

        const wrapper = shallowMount(DashboardWrapper)

        wrapper.setData({
            deleteModalOpen: true
        })

        const value = wrapper.find(".dashboard-wrapper__modal").vm.value

        expect(value).toEqual(true)

EDIT: I just checked and you can even do wrapper.find(".dashboard-wrapper__modal").props().value just fine. In fact, I've updated my tests to check against .props() instead of the .vm

@ebisbe
Copy link
Collaborator

ebisbe commented Feb 15, 2023

#1564 (comment)

@ebisbe ebisbe closed this as completed Feb 15, 2023
@khaledosman
Copy link

khaledosman commented Sep 22, 2023

If you're stubbing the component because its lazily loaded, you can pass the actual component implementation directly as the stub instead of setting it to true

Before:

const wrapper = shallowMount(ParentComponent, {
  stubs: {
    ChildComponent: true
  },
});

After:

import ChildComponent from '@/components/ChildComponent'
const wrapper = shallowMount(ParentComponent, {
  stubs: {
    ChildComponent: ChildComponent
  },
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants