Stubbing / Spy on global.fetch in Deno - unit-testing

I'm just getting into Deno, one of the things I'm a little unsure about is how to stub or create a spy for the global fetch function?
One solution is to simply wrap the fetch in a function which itself can by stubbed or spied on, but that seems like an unnecessary abstraction.
Any help would be much appreciated.

With denock you can mock the return object of the fetch call. Maybe not what you want but now you can test without a real call to the server.
https://deno.land/x/denock#0.2.0
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { denock } from "https://deno.land/x/denock/mod.ts";
// function to test
async function fetchFromServer() {
const urlObject = new URL("https://jsonplaceholder.typicode.com/todos");
const response = await fetch(urlObject, {
method: "POST",
headers: new Headers({
"content-type": "application/json",
}),
body: JSON.stringify({
userId: 2,
id: 23024,
title: "delectus aut autem",
completed: false,
}),
});
return await response.json();
}
// mock return
denock({
method: "POST",
protocol: "https",
host: "jsonplaceholder.typicode.com",
headers: [
{
header: "content-type",
value: "application/json",
},
],
path: "/todos",
requestBody: {
userId: 2,
id: 23024,
title: "delectus aut autem",
completed: false,
},
replyStatus: 201,
responseBody: { example: "My mocked response" },
});
// test
Deno.test("fetch", async () => {
const actual = await fetchFromServer();
assertEquals({ example: "My mocked response" }, actual);
});

Related

for some reason my put route is not working in sequelize

this is my call
$.ajax({
url: "/api/user_data",
method: "PUT",
data: {
id: data.id,
level: newLevel,
point: addPoints
},
error: function(req, err) {
console.log(err)
}
}).then(result => {
console.log("user info updated");
console.log(result);
// window.location.replace("/members");
});
this is my route
app.put("/api/user_data", function(req, res) {
db.User.update(
{
points: req.body.points,
level: req.body.level,
},
{
where: {
id: req.body.id,
},
}
).then(function(result) {
res.json(result);
});
});
I am also getting an error saying "can't set headers after they are sent".
I had { force: true } set before and it was not working. When I set force: false the level would update but only after I logged out then logged back in. I'm not sure why that was happening at all but any help would be much appreciated.

chart js not dispalying data array that comes from an axios request

I have an API end point that returns an array of 24 values that I want to use in my chartjs within a vue component.
when the page loads I get no errors but the bars on the charts just don't show and I don't know why.
EDIT: I noticed that the async function returns a promise instead of the actual data:
async filterData() {
await this.$axios.get('/api/data_app/job_count_by_hour/')
.then(response => {
return this.chart_data = response.data;
})
}
here is the data return code, I have a function that populates the chart_data array :
data(){
return {
form:{
day: 'select day',
workspace:'',
machine_family: [],
duration: []
},
res: [],
total:[],
chart_data: [],
url: '/api/jobs/job_count_by_hour/',
days: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "sunday"],
barChart2: {
labels: ["6h", "7h", "8h", "9h","10h","11h", "12h", "13h", "14h", "15h", "16h", "17h", "18h", "19h", "20h", "21h","22h", "23h", "00h"],
datasets: [{
label: ["popularity"],
backgroundColor:"#f93232" ,
data: this.chart_data
},],
},
}
},
methods: {
async filterData() {
let _url = `${this.url}`
await this.$axios.get(_url)
.then(response => {
this.chart_data = response.data;
})
return this.chart_data
},
},
mounted() {
this.filterData()
}
}
this is the chart component:
<script>
import { Line } from 'vue-chartjs'
export default {
extends: Line,
props: {
chartdata: {
type: Object,
default: null
},
options: {
type: Object,
default: null
}
},
mounted () {
this.renderChart(this.chartdata, this.options)
}
}
in the parent component It looks like this:
en <BarChart :labels="barChart2.labels"
:datasets="barChart2.datasets"
:height="100"
>
</BarChart>ter code here
Turns out that when you try to update nested data, the component doesn't re-render.
This is how I solved it, I put the entire object in an update function and call that function when i get my data from the back end, I hope this helps!:
methods: {
onInput(value) {
this.filterData()
},
updateChart(data) {
this.datasets = [{
label: ["popularity"],
backgroundColor:"#f93232",
data: data
}]
},
async loadData() {
await this.$axios.get(this.url)
.then(response => {
this.updateChart(response.data)
})
},
},
mounted() {
this.loadData()
},

Loopback 4 REST Controller path returns NotFoundError 404

Created a REST Controller with CRUD functions object via the CLI using
lb4 controller media
pointing to an existing MediaRepository for an existing Entity Media model
both of which were generated using the lb4 CLI as well.
A MediaController class was created with all of the REST routes for /media*
The /ping route works fine so I looked for any special routing configuration for it to see if there might be a config messing for /media but nothing was obvious.
An HTTP Get request to /media response with a web page having the following content:
<h1>NotFoundError</h1>
<h2><em>404</em> Endpoint "GET /media" not found.</h2>
There is probably some fundamental configuration or setup that needs to happen but I am just not seeing it.
MediaController class
import {
Count,
CountSchema,
Filter,
repository,
Where,
} from '#loopback/repository';
import {
post,
param,
get,
getFilterSchemaFor,
getWhereSchemaFor,
patch,
put,
del,
requestBody, Request, RestBindings, ResponseObject
} from '#loopback/rest';
import { Media } from '../models';
import { MediaRepository } from '../repositories';
export class MediaController {
constructor(
#repository(MediaRepository)
public mediaRepository: MediaRepository,
) { }
#post('/media', {
responses: {
'200': {
description: 'Media model instance',
content: { 'application/json': { schema: { 'x-ts-type': Media } } },
},
},
})
async create(#requestBody() media: Media): Promise<Media> {
return await this.mediaRepository.create(media);
}
#get('/media/count', {
responses: {
'200': {
description: 'Media model count',
content: { 'application/json': { schema: CountSchema } },
},
},
})
async count(
#param.query.object('where', getWhereSchemaFor(Media)) where?: Where<Media>,
): Promise<Count> {
return await this.mediaRepository.count();
}
#get('/media', {
responses: {
'200': {
description: 'Array of Media model instances',
content: {
'application/json': {
schema: { type: 'array', items: { 'x-ts-type': Media } },
},
},
},
},
})
async find(
#param.query.object('filter', getFilterSchemaFor(Media)) filter?: Filter<Media>,
): Promise<Media[]> {
return await this.mediaRepository.find(filter);
}
#patch('/media', {
responses: {
'200': {
description: 'Media PATCH success count',
content: { 'application/json': { schema: CountSchema } },
},
},
})
async updateAll(
#requestBody() media: Media,
#param.query.object('where', getWhereSchemaFor(Media)) where?: Where<Media>,
): Promise<Count> {
return await this.mediaRepository.updateAll(media, where);
}
#get('/media/{id}', {
responses: {
'200': {
description: 'Media model instance',
content: { 'application/json': { schema: { 'x-ts-type': Media } } },
},
},
})
async findById(#param.path.string('id') id: string): Promise<Media> {
return await this.mediaRepository.findById(id);
}
#patch('/media/{id}', {
responses: {
'204': {
description: 'Media PATCH success',
},
},
})
async updateById(
#param.path.string('id') id: string,
#requestBody() media: Media,
): Promise<void> {
await this.mediaRepository.updateById(id, media);
}
#put('/media/{id}', {
responses: {
'204': {
description: 'Media PUT success',
},
},
})
async replaceById(
#param.path.string('id') id: string,
#requestBody() media: Media,
): Promise<void> {
await this.mediaRepository.replaceById(id, media);
}
#del('/media/{id}', {
responses: {
'204': {
description: 'Media DELETE success',
},
},
})
async deleteById(#param.path.string('id') id: string): Promise<void> {
await this.mediaRepository.deleteById(id);
}
}
So I set lb4 aside for a while while I evaluated other frameworks.
Came back to my lb4 demo project today. No changes to anything since then. Started the application.
npm run start
Browsed to localhost:3000/media
To my surprise it returned a json response. Now my response array was empty and it should have returned something as there were documents in the mongodb datasource but that is a separate issue to figure out.

loopback rest connection headers authorization

I'm trying to access the Shopify Orders API in a Loopback application. I have the following data source:
"ShopifyRestDataSource": {
"name": "ShopifyRestDataSource",
"connector": "rest",
"operations": [{
"template": {
"method": "GET",
"url": "https://mystore.myshopify.com/admin",
"headers": {
"accepts": "application/json",
"content-type": "application/json"
}
},
"headers": {
"Authorization": "Basic MzdiOD..."
},
"functions": {
"find": []
}
}]
}
And then I attempt a simple call:
var ds = app.dataSources.ShopifyRestDataSource;
ds.find(function(err, response, context) {
if (err) throw err;
if (response.error) {
next('> response error: ' + response.error.stack);
}
console.log(response);
next();
});
I'm getting the following exception message:
Error: {"errors":"[API] Invalid API key or access token (unrecognized login or wrong password)"}
at callback (/order-api/node_modules/loopback-connector-rest/lib/rest-builder.js:529:21)
The Shopify API authenticates by basic HTTP authentication and I'm sure my request works since the same data works with curl. What am I doing wrong?
I couldn't find the "Loopback way" to do this and I couldn't wait, so I just wrote a simple https Node call. I'll paste this in here but I won't accept it as the answer. I'm still hoping someone will provide the right answer.
let response;
const options = {
hostname: 'mystore.myshopify.com',
port: 443,
path: '/admin/orders.json',
method: 'GET',
auth: `${instance.api_key}:${instance.password}`
};
const req = https.request(options, (res) => {
res.setEncoding('utf8');
let body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
let jsonResponse = JSON.parse(body);
// application logic goes here
response = 'ok';
});
});
req.on('error', (e) => {
response = e.message;
});
req.end();

karma mock promise response

I have the following factory that I'd like to test:
angular
.module('enigma.authFactory', [])
.factory('authFactory', authFactory);
authFactory.$inject = ['$http'];
function authFactory($http){
function doesUserExist(email){
return $http.post('/doesUserExist', email)
.success(function(data){
if(data !== 'user exists'){
return false;
} else {
return true;
}
});
}
}
So I wrote the following test:
describe('Auth Service Tests', function() {
var $httpBackend, defer, doesUserExistReqHandler;
beforeEach(inject(function(_$httpBackend_, $injector, $q) {
$httpBackend = _$httpBackend_;
defer = $q.defer();
doesUserExistReqHandler = $httpBackend.when('POST', '/doesUserExist').respond(defer.promise);
}));
describe('authFactory.doesUserExist()', function() {
it('should return true is a user exists', function() {
user = {
email: 'bwayne#wayneenterprise.com'
};
$httpBackend.whenPOST('/doesUserExist', user).respond('user exists');
var doesUserExist = authFactory.doesUserExist(user);
$httpBackend.flush();
expect(doesUserExist).toEqual(true);
});
});
});
I checked inside the authFactory.doesUserExist function and I am correctly getting the data set to 'user exists' which routes the function to return true. However in the unit test authFactory.doesUserExist is being set to the following object.
Expected Object({ $$state: Object({ status: 1, pending: undefined, value: Object({ data: Object({ $$state: Object({ status: 0 }) }), status: 200, headers: Function, config: Object({ method: 'POST', transformRequest: [ Function ], transformResponse: [ Function ], paramSerializer: Function, url: '/doesUserExist', data: Object({ email: 'bwayne#wayneenterprise.com' }), headers: Object({ Accept: 'application/json, text/plain, */*', Content-Type: 'application/json;charset=utf-8' }) }), statusText: '' }), processScheduled: false }), success: Function, error: Function }) to equal true.
I'm thinking the issue is that the test isn't resolving the promise properly and so I'm setting the res variable before authFactory.doesUserExist has returned true.
How would I go about fixing this?
So a few things needs to happen to get your code to work with what you have.
Here is a demo http://plnkr.co/edit/4GvMbPJgc0HcJcgFZ4DL?p=preview
Your service (factory) needs to return an object.
You are not returning a promise after your $http post.
I recommend you use the $q service.
In the testing
You need to import your module.
Be sure to inject your service
You should remove $httpBackend.when('POST', '/doesUserExist').respond(defer.promise); since it is not accomplishing anything and it is actually getting it confused in other $httpBackend.whenPost.
You should be asserting the response data instead of the promise, because authFactory.doesUserExist(user) returns a promise.
Code:
var doesUserExist = authFactory.doesUserExist(user)
.then(function (data) {
responseData = data;
});
$httpBackend.flush();
expect(responseData).toEqual(true);