How to test vue component method with ajax request in Mocha? - vue.js

I have a vue component with methods send out ajax request, I tried done() provided by Mocha to test this method, but it seems do not work correctly by the coverage report(the success and complete methods are not covered according to the report). The codes are as follows:
Vue component code in demo.vue
```
loadData: function(cb) {
var that = this;
$.ajax(
{
url: "/api/nodesWithTask",
async: true,
success: function(data) {
that.nodes = data;
},
complete: function(xhr, status) {
if (cb && typeof (cb) == 'function') {
cb.call();
}
}
}
);
}
```
Test code
```
import Vue from 'vue'
import demo from '#/components/demo'
describe('demo.vue', () => {
var mockData = {"id":"abc"};
var server;
var vm;
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
after(function () { server.restore(); });
it('load data async', (done) => {
vm.loadData(function(){
done();
});
})
})
```
Thanks for any suggestion in advance.

Advised by my leader, I find one magic config option of sinon fake server as follows
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
server.respondImmediately = true;
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
After I set the respondImmediately option to true, the test can run correctly.

Related

How do I cancel all pending Axios requests in React Native?

My React Native app uses axios to connect to a backend. This is done in the file myApi.js:
class client {
axiosClient = axios.create({
baseURL: example.com,
});
async get(url, data, config) {
return this.axiosClient.get(url, data, config);
}
async put(url, data, config) {
return this.axiosClient.put(url, data, config);
}
async post(url, data, config) {
return this.axiosClient.post(url, data, config);
}
}
export default new client();
I have a component which contains a useEffect which is controlled by a date picker. The selected date is held in a context called DateContext. When the selected date changes, a request is fired off to get some data for that date. When data is returned, it is displayed to the user.
The Component is:
const DaySelect = () => {
const {dateState} = useContext(DateContext);
useEffect(() => {
const load = () => {
const url = '/getDataForDate';
const req = {
selectedDate: moment(dateState.currentDate).format(
'YYYY-MM-DD',
),
};
myApi
.post(url, req)
.then((res) => {
// Now put results into state so they will be displayed
})
};
load();
}, [dateState.currentDate]);
return (
<View>
<>
<Text>Please select a date.</Text>
<DateSelector />
<Results />
</>
)}
</View>
);
};
export default DaySelect;
DateSelector is just the Component where the date is selected; any change to the date updates the value of dateState.currentDate. Results displays the data.
This works fine as long as the user clicks a date, and waits for the results to show before clicking a new date. However, if they click several times, then a request is fired off each time and the resulting data is displayed as each request completes. Since the requests don't finish in the order that they start, this often results in the wrong data being shown.
To avoid this, I believe I need to cancel any existing requests before making a new request. I've tried to do this using Abort Controller, but it doesn't seem to do anything.
I added the following to myApi.js:
const controller = new AbortController();
class client {
axiosClient = axios.create({
baseURL: example.com,
});
async get(url, data, config) {
return this.axiosClient.get(url, data, config);
}
async put(url, data, config) {
return this.axiosClient.put(url, data, config);
}
async post(url, data, config) {
return this.axiosClient.post(url, data, config, {signal: controller.signal});
}
async cancel() {
controller.abort();
}
}
export default new client();
Then in my main component I do
myApi.cancel()
before making the new request.
This doesn't seem to do anything, though - the requests don't get cancelled. What am I doing wrong here?
EDIT: Following Ibrahim's suggestion below, I changed the api file to:
const cancelTokenSource = axios.CancelToken.source();
class client {
axiosClient = axios.create({
baseURL: example.com,
});
async post(url, data, config) {
const newConfig = {
...config,
cancelToken: cancelTokenSource.token
};
return this.axiosClient.post(url, data, newConfig);
}
async cancel() { // Tried this with and without async
cancelTokenSource.cancel();
}
}
export default new client();
This makes the api call fail entirely.
Step1: Generate cancel token
const cancelTokenSource = axios.CancelToken.source();
Step2: Assign cancel token to each request
axios.get('example.com/api/getDataForDate', {
cancelToken: cancelTokenSource.token
});
// Or if you are using POST request
axios.post('example.com/api/postApi', {data}, {
cancelToken: ancelTokenSource.token,
});
Step3: Cancel request using cancel token
cancelTokenSource.cancel();
In myApi I used AbortController to ensure that any cancellable requests are aborted when a new cancellable request comes in:
let controller = new AbortController();
class client {
axiosClient = axios.create({
baseURL: example.com,
});
async post(url, data, config, stoppable) {
let newConfig = {...config};
// If this call can be cancelled, cancel any existing ones
// and set up a new AbortController
if (stoppable) {
if (controller) {
controller.abort();
}
// Add AbortSignal to the request config
controller = new AbortController();
newConfig = {...newConfig, signal: controller.signal};
}
return this.axiosClient.post(url, data, newConfig);
}
}
export default new client();
Then in my component I pass in 'stoppable' as true; after the call I check whether the call was aborted or not. If not, I show the results; otherwise I ignore the response:
useEffect(() => {
const load = () => {
const url = '/getDataForDate';
const req = {
selectedDate: moment(dateState.currentDate).format(
'YYYY-MM-DD',
),
};
myApi
.post(url, req, null, true)
.then((res) => {
if (!res.config.signal.aborted) {
// Do something with the results
}
})
.catch((err) => {
// Show an error if the request has failed entirely
});
};
load();
}, [dateState.currentDate]);

Testing a cloudflare worker with HTMLRewriter fails as its undefined

I have a test to test my cloudflare worker that looks like this:
const workerScript = fs.readFileSync(
path.resolve(__dirname, '../pkg-prd/worker.js'),
'utf8'
);
describe('worker unit test', function () {
// this.timeout(60000);
let worker;
beforeEach(() => {
worker = new Cloudworker(workerScript, {
bindings: {
HTMLRewriter
},
});
});
it('tests requests and responses', async () => {
const request = new Cloudworker.Request('https://www.example.com/pathname')
const response = await worker.dispatch(request);
console.log(response);
// const body = await response.json();
expect(response.status).to.eql(200);
// expect(body).to.eql({message: 'Hello mocha!'});
});
});
In my worker I do something like this:
const response = await fetch(BASE_URL, request);
const modifiedResponse = new Response(response.body, response);
// Remove the webflow badge
class ElementHandler {
element(element) {
element.append('<style type="text/css">body .w-webflow-badge {display: none!important}</style>', {html: true})
}
}
console.log(3);
return new HTMLRewriter()
.on('head', new ElementHandler()).transform(modifiedResponse);
Now when i run my test I get this error message:
● worker unit test › tests requests and responses
TypeError: Cannot read property 'transform' of undefined
at evalmachine.<anonymous>:1:1364
at FetchEvent.respondWith (node_modules/#dollarshaveclub/cloudworker/lib/cloudworker.js:39:17)
What seems to be wrong?
HTMLRewriter i created looks like this:
function HTMLRewriter() {
const elementHandler = {};
const on = (selector, handler) => {
if (handler && handler.element) {
if (!elementHandler[selector]) {
elementHandler[selector] = [];
}
elementHandler[selector].push(handler.element.bind(handler));
}
};
const transform = async response => {
const tempResponse = response.clone();
const doc = HTMLParser.parse(await tempResponse.text());
Object.keys(elementHandler).forEach(selector => {
const el = doc.querySelector(selector);
if (el) {
elementHandler[selector].map(callback => {
callback(new _Element(el));
});
}
});
return new Response(doc.toString(), response);
};
return {
on,
transform
};
}
Since HTMLRewriter() is called with new, the function needs to be a constructor. In JavaScript, a constructor function should set properties on this and should not return a value. But, your function is written to return a value.
So, try changing this:
return {
on,
transform
};
To this:
this.on = on;
this.transform = transform;

Detox with Mirage.js [Mock API Call] React-Native

I'm working on some tests using Detox for my React-Native application, one of those test is a flow where I need to check that the user's session is secured. If not, I'm sending an SMS Verification Code.
Test : Success to mock the POST API Call api/sessions/:sessionId, {code : 123456}
Problem : Mirage is not catching the call, so of course my Saga return an error for the fake code 123456, where I want instead Mirage.JS to return true to continue the flow.
Here are the file (file.spec.js):
import { Server } from "miragejs"
import { makeServer } from "./server";
let server;
beforeEach(() => {
server = makeServer({ environment: "development" });
})
afterEach(() => {
server.shutdown()
})
describe('SecureFlow', () => {
it("should do nav to a project and start Investment Flow", async () => {
server.get("https://random-api.eu/sessions/:sessionId", () => {
return new Response( 200, {}, { ok: true });
});
await basicNavigation(); //randomNavigation until the secure part (Screen)
await element(by.id('Accept-andLend')).tap();
await element(by.id('textInput-SMSCode')).typeText("123456");
})
})
server.js
import { Server, Model, Factory } from "miragejs";
export function makeServer({ environment = "development" } = {}) {
let server = new Server({
environment,
models: {
},
routes() {
this.post("https://random-api.eu/sessions/:sessionId", schema => {
return [{ok: true}];
});
}
});
return server;
}

How use axios in web worker with vue?

I've try use axios request in web worker by vue plugin
My code looks like this:
//worker.js
import axios from 'axios';
export default function getApiData(args) {
axios.get('/api/test').then(response => {
console.log(response);
});
}
And main file with Vue
//main.js
import getApiData from './worker.js';
Vue.use(VueWorker);
window.vueApp = new Vue({
//...
created: function() {
this.updateWorker = this.$worker.create([
{
message: 'getApiData ',
func: getApiData
}
]);
this.testWorker.postMessage('getApiData ', [this.args])
.then(result => {
console.log(result);
})
},
//...
}
And I got this error
Uncaught ReferenceError: axios__WEBPACK_IMPORTED_MODULE_0___default is
not defined
What I'm doing wrong?
I had the same problem and solved it by going back to the basics - using plain old XMLRequest (see below).
You can't use axios inside the worker because the blob that's created behind the scenes to load the worker.js runs in a different context than your main.js. In order for axios to work, you would have to setup a new webpack bundler to just bundle the worker by itself. I didn't want to do that - it makes the project structure unnecessary complex.
Here is a simple solution (works with json and non-json response).
// worker.js
export default () => {
self.addEventListener('message', e => {
if (!e) return;
// wrap in try/catch if you want to support IE11 and older browsers
// that don't support Promises. The implementation below doesn't work
// even when polyfills are loaded in the main project because
// the worker runs in a different context, ie no webpack bundles here.
try {
const fetchData = (url, isJSON = true) => {
return new Promise((resolve, reject) => {
function reqListener() {
if (this.status !== 200) {
return reject();
}
const response = isJSON
? JSON.parse(this.responseText)
: this.responseText;
resolve(response);
}
const oReq = new XMLHttpRequest();
oReq.addEventListener('load', reqListener);
oReq.open('GET', url);
oReq.send();
});
};
const baseUrl = 'https://static.newsfilter.io/';
const { articleId } = e.data;
const jsonUrl = baseUrl + articleId + '.json';
const htmlUrl = baseUrl + articleId + '.html';
// My use case requires 2 requests in parallel.
const tasks = [fetchData(jsonUrl), fetchData(htmlUrl, false)];
Promise.all(tasks)
.then(data => {
postMessage({ json: data[0], html: data[1] });
})
.catch(error => {
postMessage({ isError: true, error });
});
} catch (error) {
postMessage({ isError: true });
}
});
};

Node Promises with webdriver.io

I am trying to run selenium web scraping and failing. Please look at the code snippet.
var webdriverio = require('webdriverio');
var client = webdriverio
.remote({
desiredCapabilities: {
browserName:'chrome'
}
});
module.exports = function(some_url) {
return new Promise(function(resolve) {
client
.init()
.newWindow(some_url)
.getHTML('html')
.then(function(html) {
var some_data = someFactory(html); // do some data scraping
Promise
.all(
some_data
.some_array
.map(function (data) {
return new Promise(function(resolve) {
client
.newWindow(data.other_link)
.getHTML('html')
.then(function (html) {
var $ = cheerio.load(html); // do some more scraping
resolve(html);
})
.end();
});
})
)
.then(function (data) {
// execution never comes here while I get html in
// the mapped function
resolve(some_data, data);
});
).end();
});
This above execution never completes. It never comes out of the inner block.
Any help would be greatly appreciated.