-
Notifications
You must be signed in to change notification settings - Fork 244
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
feat: expose everything on wrapper.vm to help testing script setup
#931
Conversation
6334414
to
77cbbf6
Compare
Note that I tested this patch on a project that I fully migrated to |
This commit changes `wrapper.vm` to actually point to `vm.$.proxy`. This shouldn't change the behaviour of existing tests, but allows components written with `script setup` to be tested as well, without the need to expose everything just for testing purposes. For example a component like: ```vue <script setup lang="ts"> import { ref } from 'vue' const count = ref(0) </script> ``` can now be tested like `expect(wrapper.vm.count).toBe(0)`, whereas you previously had to add `defineExpose({ count })` for this to work. The downside is that you now can't test that something is _not_ exposed by a component, but I don't think this is a problem. This also removes the previous hacks for script setup, as it looks like they are no longer necessary.
77cbbf6
to
25dfe7f
Compare
@@ -3,5 +3,6 @@ | |||
"compilerOptions": { | |||
"lib": ["DOM", "ES2020"], | |||
"skipLibCheck": true | |||
} | |||
}, | |||
"exclude": ["tests/expose.spec.ts"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
volar can't figure out that we magically expose more things on wrapper.vm
. That might be an issue long term as users may run into it as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this isn't really something than can be fixed - it makes little sense for Volar to have something special for test utils.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I think there was an issue even with regular defineExpose
(which should work), but we'll see if that's really an issue and what we can do about it.
// a test will still be able to do something like | ||
// `expect(wrapper.vm.count).toBe(1)` | ||
// (note that vm can be null for functional components, hence the condition) | ||
this.componentVM = vm ? (vm.$.proxy as T) : (vm as T) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could also consider alternatives like:
- exposing the proxy on a different field, like
wrapper.proxy
, allowing to doexpect(wrapper.proxy.count).toBe(2)
, without changingwrapper.vm
- add a mounting option to expose everything:
const wrapper = mount(Hello, { expose: true });
expect(wrapper.vm.count).toBe(2); // vm would be proxy here
- only do this for script setup components, or for components that do not expose anything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(wrapper.proxy.count).toBe(2)
doesn't seem ideal - unless you know Vue and script setup
very well, it'd be hard to understand (and even document). I think tests should be written the same way, regardless of whether you are using script setup, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I feel the same. I think testing different types of components should not change the way tests are written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I totally support @cexbrayat point - but being a devil's advocate - component implementation specific already leaks into VTU: for example you can't use data()
option for class components, you won't be able to use components
param with functional components, etc. I'm a bit concerned that we are doing exceptions here, although script setup
is designed to be "blackboxed"
Removing the hack is a great idea. I am not convinced exposing things that are intentionally unexposed is a good idea - it's like when you make a I don't think want an extra field specific to script setup - ideally our entire suite would run the same for both script setup and regular composition components. One should not need to consider such implementation details when testing. Either way, I'm guessing this won't cause any problems, so shall we merge it up? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The downside is that you now can't test that something is not exposed by a component, but I don't think this is a problem.
Not sure if a bug… or a feature 😉
As I mentioned on Discord, I think providing a way for users to easily modify the state of a component, or spy on it, is nice (that's why wrapper.vm exists). But I get your point on not exposing things, so I don't mind if we don't merge this. On the other hand, the PR should not have any impact, and we don't even need to document it: it works as expected. I think it's more surprising that |
In the interest of writing tests faster, I would definitely leverage this if J-Unit could somehow access private variables at runtime only in the test environment. JS also doesn't have public/private variables to my knowledge, so that sounds like it might be a straw-man argument. In the rfc, it sounded like the reason |
This is actually a good point. When moving to script setup, people will want to rely on their tests to help. I think we should probably merge this; the public API isn't actually changing (right, just to confirm; this is my understanding). It just makes things work with |
@lmiller1990 I confirm the API does not change. If we spot regressions, I think we can reconsider, and expose it behind a |
I have mixed feelings about it. While I still feel accessing |
Define "outside." Sure, parent components shouldn't have access to |
@micDropper I believe there is a common ground (no matter of |
@xanf There's a difference between encouraging and enforcing. Unit tests do depend on implementation details and there's no way to completely decouple them. Angular enforces everything, Vue's philosophy is that it is approachable. Some may want to test functionality through the template (VTU's api isn't the most friendly to this method of testing as it stands either, @testing-library/vue has a much friendlier api for testing through template interaction), others may want to black-box down to the method/computed level and test units of code there. It makes VTU unapproachable to unexpectedly require people to expose vm data manually just to adopt a nice sugar syntax. |
Sorry, maybe I'm missing something - where it is stated?
First of all - by any means I'm not a member of Vue/VTU team, but I see VTU mission to prevent things which are considered "not best practices". I'm taking general direction of VTU as stated on this page https://next.vue-test-utils.vuejs.org/guide/essentials/easy-to-test.html which provides "general guidance" and explicitly states that we should not test implementation details
I struggle to draw a line between "encouraging" and "enforcing". For me "enforcing" is a scenario when no escape hatches are available and this is not the case - you still can do My main concern about this change - I really would like VTU to be consistent in following goals. If Having ability to do things in multiple ways is a real pain - we constantly feel it in GitLab where VTU v1 "everything is possible" approach resulted in so many approaches in codebase resulting migration even between minor versions of VTU v1 pain and a challenge, so I really liked the direction of VTU team of taking "opinionated" approach, while maintaining (or at least not preventing) escape hatches for everything |
^^Stated on the vue.js home page. https://vuejs.org/
^^Also stated by Evan You in a podcast with egghead.io
That's comforting to hear, but there again as originally pointed out by @cexbrayat, if it's still available, why break people's tests when they migrate to
I do agree that tests should be tested through the template as much as possible. Many developers disagree though, and they often make good points too. You can encourage these developers to do it the right way without making their life harder.
If GitLab is struggling with consistency, I would say start unit testing your unit tests for behaviors you don't like (like usage of setMethods perhaps), improve testing guidance in your MR templates, emphasize code review, and consider using @testing-library/vue to help your developers test through the template easier. |
Sorry, I still believe that release of VTU 2.0 (without beta) is the rare point where we could evaluate "breaking peoples test" and it was done earlier. We can use same argumentation:
I can continue, but I believe you've got an idea. Changes are very painful, and maintaining compatibility gives us very rare opportunities "to do things in the correct way (c)" My position is straightforward - both consistency and encouraging people to write code in the way it is expected to be written are important
Thank you for your suggestions and everything you've mentioned is deployed (except Again - I'm not against this MR at all, but it would be great to hear from the team about their vision and ensure that this vision stays consistent, or we will have even more such conversations otherwise. |
I think you have explained it better than I would! 😄 When it comes to opinions on how to write tests, I think it is not black or white, but rather a spectrum. Vue Testing Library is highly opinionated. Vue Test Utils, on the other hand, is not. That doesn't mean, though, that the library should expose everything, allow everything, and accept everything. I think it is perfectly fine to favor one over others. If releasing VTU 2 gives us the opportunity to address some past decisions (through breaking changes) and help define a testing style that most of us are comfortable with, why would anyone miss the opportunity? I do believe this is what makes VTU (and Vue) "approachable". Soft learning curve, sensible defaults, coherent and helpful docs, and escape hatches for advances usages. Allowing everything isn't approachable ("Having ability to do things in multiple ways is a real pain"). |
I think we all agree that tests should not tinker too much with component internals. But in real life, it's sometimes very handy (especially to mock/spy on a function). I'm afraid that we'll have an endless stream of issues from developers trying to test their This PR does not change anything to existing projects, and it does not tell developers to use |
While I may not agree with the idea of testing internals (eg, using If we do not merge this, a user who'd like to test internals will just do If this was a new library with a clear, opinionated vision, I'd definitely not want this kind of feature. That said, I feel like at this point, VTU is old enough and widespread enough that our goal should be sustainability and long term longevity - eg, making sure things continue to work in the same manner they have done so in the past. In this case, that would mean making What does everyone else think? Happy to agree that testing against |
@lmiller1990 👍Can you cut a release with this? |
To follow up on this I think a vision or mission statement around what VTU is supposed to be is a great idea. It would definitely help guide development and decisions. |
after udpate, we get wrapper.vm is empty object. |
TS can't see the properties yet, see #972 But you can just cast as explained in the issue and it should work |
This commit changes
wrapper.vm
to actually point tovm.$.proxy
.This shouldn't change the behaviour of existing tests, but allows components written with
script setup
to be tested as well,without the need to expose everything just for testing purposes.
For example a component like:
can now be tested with
expect(wrapper.vm.count).toBe(0)
, whereas you previously had to adddefineExpose({ count })
for this to work.The downside is that you now can't test that something is not exposed by a component, but I don't think this is a problem.
This also removes the previous hacks for script setup, as it looks like they are no longer necessary.