How can I spy or mock methods in Vue with sinon? - vue.js

I'm trying to create a unit test for my app based in Vue and vue-cli with webpack template.
I read Vue documentation, vue-loader, vue-cli and vue-template/webpack (an more!). When I try to make a unit test for my component, I use this.
const Constructor = Vue.extend(MovieList)
vm = new Constructor({
propsData: {
criteria
},
methods: {
fetch: sandbox.spy()
}
}).$mount()
And test this:
expect(vm.fetch).to.be.called
But I get this error:
✗ should be called fetch
TypeError: { [Function: boundFn] _length: 0 } is not a spy or a call to a spy!
at assertCanWorkWith (/home/jmanuelrosa/Developer/personales/vue-course/movues/node_modules/sinon-chai/lib/sinon-chai.js:43:19)
at Assertion. (/home/jmanuelrosa/Developer/personales/vue-course/movues/node_modules/sinon-chai/lib/sinon-chai.js:70:13)
at Assertion.addProperty (/home/jmanuelrosa/Developer/personales/vue-course/movues/node_modules/chai/chai.js:4240:29)
at Context. (webpack:///test/unit/specs/MovieList.spec.js:74:39 <- index.js:14910:39)

Never worked with sinon but doc says:
sinon.spy(vm, "fetch");
expect(vm.fetch).toHaveBeenCalled();
http://sinonjs.org/releases/v2.1.0/spies/
Are you calling fetch upon component created? sometimes you need to manually trigger methods.

Related

How debug Vue3 applications with chrome and native javascript debugger

i started a new application with vue3 (created with vue-cli) and I'm not able to exploit native debugger in chrome
I have read that I had to specify the source map in my vue.config.js
module.exports = {
pluginOptions: {
quasar: {
importStrategy: 'kebab',
rtlSupport: false
}
},
configureWebpack: {
devtool: 'source-map'
},
transpileDependencies: [
'quasar'
]
}
But I'm still not able to explore my component code with a debugger
the result I have
screenshot not working
and what i want (screen from a vue2 project)
screenshot working
To use the native js debugger in a vue 2 application. You can do something like this:
methods: {
doSomething () {
this.loading = true
// doing something
debugger // native js debugger, in console check => this.loading (= true)
this.loading = false
}
}
Hopefully, it works the same way in vue 3.
You may be tempted to use it in the life-cycle hooks such as mounted, created ... but unfortunately, that never worked for me. Once the debugger halts the program, you can test it in the console by seeing what kind of object this identifies as.
When the native js debugger is used in a method enclosed by the methods option, it acts in a helpful and expected way. However, when it is used in a lifecycle hook like created the this object is not what you would expect it to be.
Additionals:
I actually stumbled on this question because I was looking for ways to use the native js debugger in the life cycle hooks. I'm hoping there might be vue 2 life-cycle hooks that support it.

Aurelia-testing: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'

I am using for the first time the aurelia-testing package to test my HTML code.
I seem to have followed the docs to set up my test as follows:
describe('Contextual Menu HTML View test suite', function () {
let component: ComponentTester;
beforeEach(function () {
component = StageComponent
.withResources('../../src/components/modal/contextual-menu')
.inView('<contextual-menu is-active.two-way="activateMenu"></contextual-menu>')
.boundTo({ activateMenu: false });
});
it('should not add the is-active class to the root element', async function () {
await component.create(bootstrap);
const rootElement = await waitForDocumentElement('.qa-contextual-menu');
expect(rootElement.classList.contains('is-active')).toBe(false);
});
afterEach(function () {
component.dispose();
});
});
I tried using just bind instead of two-way but that fails too.
I tried both with a document.querySelector and with waitForDocumentElement, both cases fail, but anyways I assume the error comes from earlier.
I am getting an error and I am not sure why. Could you put on the tracks to identify the root cause of the following:
TypeError: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'.
at Object.convert (/Users/lemoustachiste/work/lm-frontend/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/Node.js:573:11)
at HTMLDivElement.replaceChild (/Users/lemoustachiste/work/lm-frontend/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/Node.js:292:31)
at NodeJsDom.replaceNode (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-pal-nodejs/dist/nodejs-dom.js:95:29)
at makeElementIntoAnchor (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2432:19)
at applyInstructions (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2479:17)
at ViewFactory.create (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2707:7)
at TemplatingEngine.enhance (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:5290:24)
at /Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-framework/dist/commonjs/aurelia-framework.js:176:28
at new Promise (<anonymous>)
at Aurelia.enhance (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-framework/dist/commonjs/aurelia-framework.js:174:12)
Thanks a lot
I am not a jest user. So I simply used the scaffolded TS jest skeleton app from aurelia-cli (au new jest-skeleton --unattended --select typescript,jest,vscode). And found that to be working.
It seems the you are missing the following configuration in your jest.config.js.
testEnvironment: "node",
After only adding that, the tests started working.

Access data model in VueJS with Cypress (application actions)

I recently came across this blog post: Stop using Page Objects and Start using App Actions. It describes an approach where the application exposes its model so that Cypress can access it in order to setup certain states for testing.
Example code from the link:
// app.jsx code
var model = new app.TodoModel('react-todos');
if (window.Cypress) {
window.model = model
}
I'd like to try this approach in my VueJS application but I'm struggling with how to expose "the model".
I'm aware that it's possible to expose the Vuex store as described here: Exposing vuex store to Cypress but I'd need access to the component's data().
So, how could I expose e.g. HelloWorld.data.message for being accessible from Cypress?
Demo application on codesandbox.io
Would it be possible via Options/Data API?
Vue is pretty good at providing it's internals for plugins, etc. Just console.log() to discover where the data sits at runtime.
For example, to read internal Vue data,
either from the app level (main.js)
const Vue = new Vue({...
if (window.Cypress) {
window.Vue = Vue;
}
then in the test
cy.window().then(win => {
const message = win.Vue.$children[0].$children[0].message;
}
or from the component level
mounted() {
if (window.Cypress) {
window.HelloWorld = this;
}
}
then in the test
cy.window().then(win => {
const message = win.HelloWorld.message;
}
But actions in the referenced article implies setting data, and in Vue that means you should use Vue.set() to maintain observability.
Since Vue is exposed on this.$root,
cy.window().then(win => {
const component = win.HelloWorld;
const Vue = component.$root;
Vue.$set(component, 'message', newValue);
}
P.S. The need to use Vue.set() may go away in v3, since they are implementing observability via proxies - you may just be able to assign the value.
Experimental App Action for Vue HelloWorld component.
You could expose a setter within the Vue component in the mounted hook
mounted() {
this.$root.setHelloWorldMessage = this.setMessage;
},
methods: {
setMessage: function (newValue) {
this.message = newValue;
}
}
But now we are looking at a situation where the Cypress test is looking like another component of the app that needs access to state of the HelloWorld.
In this case the Vuex approach you referenced seems the cleaner way to handle things.

Access Vue.js plugin from main.js

I'm creating a plugin and I just wonder why I can't access it in main.js file. Here's how Auth.js looks like:
const Auth = {
install(Vue) {
Vue.prototype.$isGuest = function () {
console.log('This user is a guest.');
}
Vue.prototype.$getAuthToken = function () {
console.log('Auth token will be returned.');
}
}
}
export default Auth
This is main.js:
import Auth from '#/helper/Auth'
Vue.use(Auth)
However, when I execute console.log(this.$isGuest()), it doesn't work. It actually returns the following:
main.js?1c90:25 Uncaught TypeError: this.$isGuest is not a function
The problem is that this method works when I call it in components such as Dashboard.vue and things like that.
I have a way to avoid calling isGuest method within main.js (I can call it in Layout.vue), but I'm more curious why it doesn't work in main.js.
Maybe because Vue hasn't been initialized yet, but even if I put the console.log() line at the end of the file, still doesn't work.
Thanks,
N.
If you are calling this.$isGuest() outside of Vue, you will get the error you describe. That's because this is not a Vue object. What this is depends on how you are building your code, but given you are using import it's probably the module.
Also, you are adding $isGuest to the prototype of Vue. That means that the function is only going to be available on actual instances of Vue objects. That is why it is available in your components.
If you want to use it in the main script, the only place you will be able to get to it is inside the Vue object in a lifecycle handler, method, or computed. For example:
new Vue({
mounted(){
console.log(this.$isGuest()) // this should work
}
})

How to create unit test cases with Vue, Karma, browserify

I am trying to build some unit test cases to my existing Vue project.
I found some documents there but not useful especially for testing on functions such as Watch, Promise and Then.
Is there any specific and detailed guide line on unit testing with Vue and these plugins?
The target vue has defined a function named test.
const vm = new Vue(target).$mount();
vm.test("message");
But the error message is vm.test is not a function
I do not know why I could not use the function defined in the target.vue.
Meanwhile once I use the test function to change some data, the target vue will update the data automatically.
But it seems that Vue.nextTick does not work on this situation.
Could someone help me on this point?
Thank you very much for your help.
Hellocomponent
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App',
test: 'Testing'
}
}
}
Hello.spec.js //for testing Hello.vue
describe('Hello', () => {
it('set correct default data', () => {
expect(typeof Hello.data).to.equal('function')
assert.typeOf(Hello.data, 'function')
const defaultdata = Hello.data()
expect(defaultdata.test).to.be.a('string')
expect(defaultdata.test).to.equal('Testing')
})
})
This is test case of Hello component of vue.js which is created automatically when new template is created. This is using Karma+Mocha+Chai.