Skip to content

New type definitions for TypeScript are missed #1529

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
Ky6uk opened this issue May 5, 2020 · 35 comments · Fixed by #1530
Closed

New type definitions for TypeScript are missed #1529

Ky6uk opened this issue May 5, 2020 · 35 comments · Fixed by #1530

Comments

@Ky6uk
Copy link

Ky6uk commented May 5, 2020

Version

1.0.0

It looks index.d.ts isn't updated yet. After upgrading to v1.0.0 I started to get deprecation errors, but methods like findAllComponents and findComponent are missed in index.d.ts.

@lmiller1990
Copy link
Member

Oops. We should add those definitions.

@lmiller1990
Copy link
Member

I can fix this later today. 🤦 and do 1.0.1 straight away.

Or if you have time before me you can send a PR and I can get it merged up.

@lmiller1990
Copy link
Member

Can someone please review? #1530

@jmariller
Copy link

Also I believe the new attachTo is missing in the MountOptions interface, could this be fixed too in 1.0.1?

@lmiller1990
Copy link
Member

lmiller1990 commented May 6, 2020

@jmariller yep, I fixed that too. It should be fixed now: https://www.npmjs.com/package/@vue/test-utils 1.0.1 is out.

LMK if I missed anything.

@jmariller
Copy link

@lmiller1990 thanks a lot for the quick fix & feedback!

I shortly checked and it seems something is still missing in the definitions for mount, I believe the following interface should be adjusted (e.g. attachedTo is missing):
image
Or am I missing/misunderstanding something?

@lmiller1990
Copy link
Member

I am new to TS, I probably messed up.

I will fix it right now.

Funny how no-one noticed bugs until 1.0 drops ):

@lmiller1990
Copy link
Member

Ok.... try getting 1.0.2 and see if that fixed it for you.

Thanks! If not, lmk and I'll look into it.

@jmariller
Copy link

Works now! Thank you so much @lmiller1990 for the quick fix, really appreciate it!

And thanks in general for these utils, they are great :)

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

@lmiller1990 Now I see deprecations about methods and setMethods, but I can't find the solution to migrate. Can we add suggestions about how to fix that in addition to warnings also?

@afontcu
Copy link
Member

afontcu commented May 6, 2020

@Ky6uk hi! We removed setMethods in VTUnext because it might lead to flaky tests – your tests end up relying on implementation details. The suggestion is to rethink those tests, I believe!

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

@afontcu the test is very simple. I'm trying to $emit an action (click, for example), and checking a method has been called afterward, without any implementation details. Basically it just something like wrapper.setMethods({ onCick: mockedFn }) and mockedFn.hasBeenCalled(). So setMethods had been useful for that purpose.

@dobromir-hristov
Copy link
Contributor

Try using jest.spyOn

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

@dobromir-hristov on what property? It doesn't seem easy to avoid deprecation atm. The most convenient way regarding documentation to check a method without firing the implementation is to use setMethods.

it('triggers click', () => {
    const mock = jest.fn();

    const wrapper = shallowMount(Component);
    wrapper.setMethods({ onClick: mock });

    const linkWrapper = wrapper.findComponent({ name: 'Link' });

    expect(mock).toBeCalledTimes(0);

    linkWrapper.vm.$emit('click');

    expect(mock).toBeCalledTimes(1);
  });

@dobromir-hristov
Copy link
Contributor

So instead of checking whether your logic is correct and the right event is emitted or dom is changed from that click action, you just want to see if you are listening to it properly? Then write another separate test to actually test your logic? Isn't that double the work?

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

It just an example. The test itself may check that the emits will call the correct methods. For example, one click will call this method, double click will call another method, or the same method with a different argument, etc. And I shouldn't think about the implementation of the methods because of the logic inside too complicated and tested by different unit tests. That the idea. It's not double work, it's separating tests for complicated logic aside component tests.

@dobromir-hristov
Copy link
Contributor

Hm what about const spy = jest.spyOn(Component.methods, "yourMethod")

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

It doesn't work. Cannot spyOn on a primitive value; undefined given. I also thought about mocking wrapper.vm, but it seems read-only property. Also, it requires some crutches in TypeScript.

@afontcu
Copy link
Member

afontcu commented May 6, 2020

I'd say the component calling a method on an event is precisely an implementation detail, isn't it? The side-effects of this method (HTTP requests, cookies, other emitted events…) are the visible part of the interaction, and the one I'd test.

Can you provide a reproduction link (codesandbox or similar)? Happy to help.

@Ky6uk
Copy link
Author

Ky6uk commented May 6, 2020

@afontcu component can not be changed after an interaction. It can just trigger another logic under the hood. And my case is to be sure all triggers are calling the right methods in the component. That's it.

I have not used codesandbox before, I will check it's possible to add a quick test for that.

Upd: I've tried to create a sandbox, but it doesn't seem to work.

@afontcu
Copy link
Member

afontcu commented May 6, 2020

I've tried to create a sandbox, but it doesn't seem to work.

In this (contrived, of course!) example, I'd say that the interesting behaviour from the outside is that the component logs something after a click. For that, using a method or simply calling console.log from the template (or any other alternative) is an implementation detail, and something that, if changed, should not break your test.

In that test, I would suggest mocking/spying console.log, and assert it's been called right after triggering a click.

@Ky6uk
Copy link
Author

Ky6uk commented May 7, 2020

Yes, It makes sense. But it will also require to rewrite a bunch of tests before upgrading to vtu 2.0. The mocking methods directly still look useful in some cases to not remove them completely. Anyway, thanks to clarify.

@robokozo
Copy link

robokozo commented May 7, 2020

I was led here from this thread: #1536

Unfortunately, not being able to use the methods mounting object makes it so that testing components that make calls to methods during the mounted/created hooks a little more difficult.

In my particular case, I have a component that during created calls a method recursively. In my tests, I was able to resolve this issue my making use of the methods option:

const wrapper = shallowMount(Page, { methods: { recursivePoll: jest.fn() } })

I'm not sure how to proceed without the ability to override it for any of my mounted tests.

@afontcu
Copy link
Member

afontcu commented May 7, 2020

Hi @robokozo! That sounds interesting. Could you share a reproduction link or a repository to take a look at this use case?

@robokozo
Copy link

robokozo commented May 7, 2020

@afontcu No I can't... how embarrassing ^^ I can't reproduce it in an isolated environment because of a bug in the original code I was referring to. It looks good! Keep it up the great work! (The vue testing team should consider opening themselves up for github sponsorships)

@lmiller1990
Copy link
Member

lmiller1990 commented May 7, 2020

One immediately work around that comes to mind (maybe not ideal) would be to put said method in another file and import it. Then you could use jest.mock. You could use a mixin:

// ... before
created() {
  this.recurse()
}

// ... after

import { recurse } from './mixin.js'
mixins: [recurse]

// ... test
const mockRecurse = jest.fn()
jest.mock('./mixin.js', () => ({
  recurse: mockRecurse
}))

To do this, the variable passed into jest.mock must begin with mock or Jest will complain.

Maybe not ideal, but I think the situation you describe is one of the few edge cases where there is no direct migration path.

When you provide a minimal example, maybe we can find another option.

@SchmidtDawid
Copy link

SchmidtDawid commented Nov 2, 2020

I have functions to render correct data used like this:

 <tr>
    <td><b>{{ $t('DASHBOARD.SEASON.END') }}</b></td>
    <td>{{ convertServerZoneToUserZone(season.end, 'L') }}</td>
</tr>

convertServerZoneToUserZone function is from mixin. But i don't need to test it in component. Register it as jest.fn() was fast and useful. How to mock this function? Without mocking it i can not to render component and test it.

@lmiller1990
Copy link
Member

If it is from a mixin, you are doing import { convertServerZoneToUserZone } from './some-mixin.js'? Can you just mock the module?

jest.mock('./some-mixin', () => ({ 
  convertServerZoneToUserZone: () => {} 
}))

@SchmidtDawid
Copy link

@lmiller1990 I was trying like that

import mixinMoment from '@/mixins/mixinMoment';

jest.mock('@/mixins/mixinMoment', () => ({
  convertServerZoneToUserZone: jest.fn(),
}));

and then i got error:
[Vue warn]: Error in render: "TypeError: _vm.convertServerZoneToUserZone is not a function"

I add mixin to shallowMount, try 'mount' also, but it changes nothing

@lmiller1990
Copy link
Member

Assuming that mixins is a method you probably need to do:


jest.mock('@/mixins/mixinMoment', () => ({
  methods: {
    convertServerZoneToUserZone: jest.fn()
  }
}));

@SchmidtDawid
Copy link

SchmidtDawid commented Nov 3, 2020

@lmiller1990 🤦 of course it works now. Sometimes i feel so stupid ;) Thanks!

But i have another problem. I have button. After click it runs function. And that function runs another function. But this last function is from plugin. So i should spy on first function, and then mock function from plugin. But if i do that like this:

jest.mock('vue', () => ({
  toasted: {
    error: jest.fn()
  },
}));

Vue stops working because of lack of Vue.use()

summary: Buttons runs funcion a(). Function a runs function b(), and function b runs toasted which i cant mock. How to handle that?

@lmiller1990
Copy link
Member

Please share the function that runs the plugin code

@SchmidtDawid
Copy link

SchmidtDawid commented Nov 4, 2020

validate() {
        let valid = true;

        if (this.bet === null) {
          valid = false;
          this.$toasted.error(this.$t('GAMES.BETS.INVALID_NO_BET'));
        }

        if (this.stake < this.minBet || this.stake > this.maxBet) {
          valid = false;
          this.$toasted.error(
              this.$t(
                  'GAMES.BETS.INVALID_STAKE',
                  {
                    minBet: this.minBet.toLocaleString(this.$i18n.realLocale),
                    maxBet: this.maxBet.toLocaleString(this.$i18n.realLocale)
                  }
              )
          );
        }

        return valid;
      },

And the problem is this.$toasted.error(). If i mock vue module like

jest.mock('Vue', () => ({
  toasted: {
    error: jest.fn()
  }
}))

This function is mocked. But i had errors like Vue.use is not a function etc. I broke whole instance then i think. But otherwise mocking many functions only for check if button runs function a() is not good in my opinion. Register function is fast end easy. I really don't understand why i cant use it.

@lmiller1990
Copy link
Member

This is not how jest.mock works. You cannot "partially" mock something like this - you either mock the entire module, or you don't.

Can you use mocks? eg:

const errorMock = jest.fn()

wrapper = mount(Foo, {
  mocks: {
    $toasted: {
      error: errorMock
    }
})

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

Successfully merging a pull request may close this issue.

7 participants