I noticed that some developers are using so-called "services" to manage front end requests, for example:
const httpService = {
get(url) {
fetch(url).then((response) => {
if (!response.ok) {
throw new Error('error')
}
return response.json()
})
}
}
const getPostService = (url) => { // this is the service
return httpService.get(url).then((json) => json)
}
getPostService('/post')
.then(r => setData(r))
.catch(e => setError(e));
Is there a reason to create multiple services like: getPostService or getUserService, or getDataService, or the implementation is the same if inside the code will do:
httpService.get(url).then((json) => setData(json)).then(e => setErr(e))
Is a reason to create services or they are redundant in my case being enough to fetch data only using httpService without creating many services in my app?
Your example is strange, and doesn't make a ton of sense to me.
It doesn't look like your getPostService returns any service, it literally just wraps the httpService.get function, with no real benefit.
You can see this even clearer when cleaning up the function from:
const getPostService = (url) => { // this is the service
return httpService.get(url).then((json) => json)
}
To:
const getPostService = url => httpService.get(url);
However, if this function was called getPosts or it returned a useful object that does operations related to posts, it makes a lot of sense.
For example, maybe your PostService kinda looks like this:
class PostService {
getPosts() {}
updatePost() {}
deletePost() { }
}
Now your service has some methods that have useful behavior and that can be re-used. If multiple of your components need a 'list of posts', and if all those components just do HTTP requests, then you need to change all components if you want to change something about how 'lists of posts' are fetched.
Putting this in a central place increases maintainability
Related
There is a requirement of cancelling the request calls when navigating away from the page or when the same api call is made multiple calls ( keeping the last one active).
This is how the API is extracted out( just a high level)
AJAX.ts
export async function customAjax(options){
let options = {};
options.headers = { ...options.headers, ...obj.headers };
const response = await fetch(url, options);
await response.json()
}
GET and POST calls are being extracted as
API.ts
const get = (url, extra = {}) => request({ url, type: "GET", ...extra });
const post = (url, payload, extra = {}) => request({ url, data: payload ,type: "POST",
}, ...extra });
In the react component I call these utilities as follows:
function MyComponent(){
useEffect(() => {
makeCall();
}, []);
async function makeCall(){
const { response, error } = await API.post(URL, payload);
// Handling code is not added here
// In the similar fashion GET calls are also made
}
}
I have come across Abortcontroller to cancel request where we could use abort method during unmounting of the component.
Is there a way to do this at a utililty level, may be inside customAjax so that I could avoid writing abort controller code everywhere?
From my understanding... What you describe is no different than a memory leak issue. And the current method for avoiding memory leaks is with the AbortController().
As far as handling this at the "utility level", I don't think this is feasible, and indeed would go against the preferred notion of an api being unaware of what's going on at the React component level; i.e separation of concerns..
So, in order to accomplish your requirement, you'll need to use AbortController(), or a custom implementation using a boolean flag that reflects whether the component is mounted, on a per component basis.
Using the boolean flag, you may be able to accept an argument in your api, passing the flag as a parameter; but again, I think this would be considered an anti-pattern.
I understand you're looking for a minimal implementation; but standard practice is fairly minimal:
useEffect(() => {
let abortController = new AbortController();
// Async code
return () => { abortController.abort(); }
}, []);
Using a boolean flag would be more verbose, and would entail something like this in your case:
useEffect(() => {
let isMounted = true;
customAjax(isMounted);
return () => {
isMounted = false;
}
}, []);
To handle out-of-order ajax responses, you can use a local variable inside the effect. For example,
useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
if (!ignore) setProduct(json);
}
fetchProduct();
return () => { ignore = true };
}, [productId]);
The ignore variable will ensure that only the latest request's response is updated to state. Reference - https://reactjs.org/docs/hooks-faq.html#performance-optimizations
Regarding memory leak concerns, please see this discussion - https://github.com/reactwg/react-18/discussions/82
What I'm trying to accomplish
I am currently trying to create a wrapper for a db connection (to Neo4j) that works similar to the following:
Instantiate driver
Expose the executor for the driver so a session can be created
Pass my logic
Close the connection
Since there's no destructor in JavasScript, it's making this unnecessarily difficult with properly closing the session. The logic for creating and closing a connection is extremely repetitive and I'm trying to simplify repetitive scripts so that it's easier to call.
What I've tried.
Inject promise in chain
I thought something like the following could work, but I just cannot seem to create the logic properly. Passing session back to my inserted promise is challenging.
const connect = () => {
var driver;
var session;
return Promise.resolve(() => {
driver = my.driver(uri, creds);
}).then(() => {
// insert my promise here, exposing driver.session() function for executor
// if possible, bind it to the session var so we can properly close it after
// this would also determine the return value
}).catch((e) => console.error(e))
.finally(() => {
session.close();
driver.close();
})
});
Create class wrapper with procedural logic
I then also tried another approach, similar to:
var driver = my.driver(uri, creds);
var session;
function exitHandler(options) {
// ...
session.close();
driver.close();
}
// I have process.on for exit, SIGINT, SIGUSR1, SIGUSR2, and uncaughtException
process.on('exit', exitHandler.bind(null, options));
// ...
class Connection extends Promise<any> {
constructor(executor: Function) {
super((resolve, reject) => executor(resolve, reject));
executor(driver.session.bind(null, this));
}
}
export default Connection;
And calling it like
// ...
const handler = async () => await new Connection((session) => {
const s = session();
// do stuff here
});
The problem with this approach is that the driver is not instantiated before session is used (and so it's undefined). It also feels a little hacky with the process.on calls.
Question
Neither method works (or any of my other attempts). How can I properly wrap db connections to ensure they're consistent and deduplicate my existing code?
A sample of the Neo4j connection script can be found here. This is, essentially, what I'm trying to deduplicate across my scripts (pass everything from line 11 to 42 - inclusive) but have the init of driver, catch, finally, session.close(), driver.close() logic in my wrapper.
Ideally, I would like to expose the session function call so that I can pass parameters to it if needed: See the Session API for more info. If possible, I also want to bind the rxSession reactive session.
A sample of the Neo4j connection script can be found here. This is, essentially, what I'm trying to deduplicate across my scripts (pass everything from line 11 to 42 - inclusive) but have the init of driver, catch, finally, session.close(), driver.close() logic in my wrapper.
OK, the above part of what you are asking is what I was able to best parse and work with.
Taking the code you reference and factoring out lines 11 to 42 such that everything outside of those is shared and everything inside of those is customizable by the caller, this is what I get for the reusable part, designed to be in a module by itself:
// dbwrapper.js
const neo4j = require('neo4j-driver')
const uri = 'neo4j+s://<Bolt url for Neo4j Aura database>';
const user = '<Username for Neo4j Aura database>';
const password = '<Password for Neo4j Aura database>';
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
let driverOpen = true;
async function runDBOperation(opCallback, sessOpts = {}) {
const session = driver.session(sessOpts);
try {
await opCallback(session);
} catch (e) {
console.log(e);
throw e;
} finally {
await session.close();
}
}
async function shutdownDb() {
if (driverOpen) {
driverOpen = false;
await driver.close();
}
}
process.on('exit', shutdownDb);
module.exports = { runDBOperation, shutdownDb };
Then, you could use this from some other module like this:
const { runDBOperation, shutdownDB } = require('./dbwrapper.js');
runDBOperation(async (session) => {
const person1Name = 'Alice'
const person2Name = 'David'
// To learn more about the Cypher syntax, see https://neo4j.com/docs/cypher-manual/current/
// The Reference Card is also a good resource for keywords https://neo4j.com/docs/cypher-refcard/current/
const writeQuery = `MERGE (p1:Person { name: $person1Name })
MERGE (p2:Person { name: $person2Name })
MERGE (p1)-[:KNOWS]->(p2)
RETURN p1, p2`
// Write transactions allow the driver to handle retries and transient errors
const writeResult = await session.writeTransaction(tx =>
tx.run(writeQuery, { person1Name, person2Name })
)
writeResult.records.forEach(record => {
const person1Node = record.get('p1')
const person2Node = record.get('p2')
console.log(
`Created friendship between: ${person1Node.properties.name}, ${person2Node.properties.name}`
)
})
const readQuery = `MATCH (p:Person)
WHERE p.name = $personName
RETURN p.name AS name`
const readResult = await session.readTransaction(tx =>
tx.run(readQuery, { personName: person1Name })
)
readResult.records.forEach(record => {
console.log(`Found person: ${record.get('name')}`)
})
}).then(result => {
console.log("all done");
}).catch(err => {
console.log(err);
});
This can be made more flexible or more extensible according to requirements, but obviously the general idea is to keep it simple so that simple uses of the common code don't require a lot of code.
I have a module, for the purposes of learning testing, that looks like this:
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
const URI_USERS = 'users/';
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => fetchUser(id)));
return users.map(user => parseUser(user));
}
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
Pretty straight forward stuff.
Now I want to test that fetchUserStrings method, and to do that I want to mock/spy on both fetchUser and parseUser. At the same time - I don't want the behaviour of parseUser to stay mocked - for when I'm actually testing that.
I run in the problem that it seems that it is not possible to mock/spy on functions within the same module.
Here are the resources I've read about it:
How to mock a specific module function? Jest github issue. (100+ thumbs up).
where we're told:
Supporting the above by mocking a function after requiring a module is impossible in JavaScript – there is (almost) no way to retrieve the binding that foo refers to and modify it.
The way that jest-mock works is that it runs the module code in isolation and then retrieves the metadata of a module and creates mock functions. Again, in this case it won't have any way to modify the local binding of foo.
Refer to the functions via an object
The solution he proposes is ES5 - but the modern equivalent is described in this blog post:
https://luetkemj.github.io/170421/mocking-modules-in-jest/
Where, instead of calling my functions directly, I refer to them via an object like:
api.js
async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
async function fetchUsers() {
return lib.makeApiCall(URI_USERS);
}
async function fetchUser(id) {
return lib.makeApiCall(URI_USERS + id);
}
async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return users.map(user => lib.parseUser(user));
}
function parseUser(user) {
return `${user.name}:${user.username}`;
}
const lib = {
makeApiCall,
fetchUsers,
fetchUser,
fetchUserStrings,
parseUser
};
export default lib;
Other posts that suggest this solution:
https://groups.google.com/forum/#!topic/sinonjs/bPZYl6jjMdg
https://stackoverflow.com/a/45288360/1068446
And this one seems to be a variant of the same idea:
https://stackoverflow.com/a/47976589/1068446
Break the object into modules
An alternative, is that I would break my module up, such that I'm never calling functions directly within each other.
eg.
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
user-api.js
import {makeApiCall} from "./api";
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
user-service.js
import {fetchUser} from "./user-api.js";
import {parseUser} from "./user-parser.js";
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return ids.map(user => lib.parseUser(user));
}
user-parser.js
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
And that way I can mock the dependency modules when I'm testing the dependant module, no worries.
But I'm not sure that breaking up the modules like this is even feasible - I imagine that there might be a circumstance where you have circular dependencies.
There are some alternatives:
Dependency injection in the function:
https://stackoverflow.com/a/47804180/1068446
This one looks ugly as though, imo.
Use babel-rewire plugin
https://stackoverflow.com/a/52725067/1068446
I have to admit - I haven't looked at this much.
Split your test into multiple files
Am investigating this one now.
My question: This is all quite a frustrating and fiddly way of testing - is there a standard, nice and easy, way people are writing unit tests in 2018, that specifically solve this issue?
As you've already discovered attempting to directly test an ES6 module is extremely painful. In your situation it sounds like you are transpiling the ES6 module rather than testing it directly, which would likely generate code that looks something like this:
async function makeApiCall(uri) {
...
}
module.exports.makeApiCall = makeApiCall;
Since the other methods are calling makeApiCall directly, rather than the export, even if you tried to mock the export nothing would happen. As it stands ES6 module exports are immutable, so even if you did not transpile the module you would likely still have issues.
Attaching everything to a "lib" object is probably the easiest way to get going, but it feels like a hack, not a solution. Alternatively using a library that can rewire the module is a potential solution, but its extremely hokey and in my opinion it smells. Usually when you're running into this type of code smell you have a design problem.
Splitting up the modules into tiny pieces feels like a poor mans dependency injection, and as you've stated you'll likely run into issues quickly. Real dependency injection is probably the most robust solution, but it's something you need to build from the ground up, it's not something that you can just plug into an existing project and expect to have things working immediately.
My suggestion? Create classes and use them for testing instead, then just make the module a thin wrapper over an instance of the class. Since you're using a class you'll always be referencing your method calls using a centralized object (the this object) which will allow you to mock out the things you need. Using a class will also give you an opportunity to inject data when you construct the class, giving you extremely fine grained control in your tests.
Let's refactor your api module to use a class:
import axios from 'axios';
export class ApiClient {
constructor({baseUrl, client}) {
this.baseUrl = baseUrl;
this.client = client;
}
async makeApiCall(uri) {
try {
const response = await this.client(`${this.baseUrl}${uri}`);
return response.data;
} catch (err) {
throw err.message;
}
}
async fetchUsers() {
return this.makeApiCall('/users');
}
async fetchUser(id) {
return this.makeApiCall(`/users/${id}`);
}
async fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => this.fetchUser(id)));
return users.map(user => this.parseUser(user));
}
parseUser(user) {
return `${user.name}:${user.username}`;
}
}
export default new ApiClient({
url: "https://jsonplaceholder.typicode.com/",
client: axios
});
Now lets create some tests for the ApiClient class:
import {ApiClient} from './api';
describe('api tests', () => {
let api;
beforeEach(() => {
api = new ApiClient({
baseUrl: 'http://test.com',
client: jest.fn()
});
});
it('makeApiCall should use client', async () => {
const response = {data: []};
api.client.mockResolvedValue(response);
const value = await api.makeApiCall('/foo');
expect(api.client).toHaveBeenCalledWith('http://test.com/foo');
expect(value).toBe(response.data);
});
it('fetchUsers should call makeApiCall', async () => {
const value = [];
jest.spyOn(api, 'makeApiCall').mockResolvedValue(value);
const users = await api.fetchUsers();
expect(api.makeApiCall).toHaveBeenCalledWith('/users');
expect(users).toBe(value);
});
});
I should note that I have not tested if the provided code works, but hopefully the concept is clear enough.
I am trying to understand how developers use Promise with React-Native. It would be great to get feedback and recommendations on how to setup API calls and handle the data. Please understand I never used Promise before and that I am new to React-Native.
Thank you in advance. Any resource about this subject is welcome too.
Pseudocode
Child
Retrieve two variables
Use these two variables to build an URL
Trigger the first Promise and resolve
Retrieve another two variables
Use these two variables to build a new an URL
Trigger the second Promise and resolve
Gather the data from both promises and pass to parent
Parent
Retrieve data from Child
Get data from the first Promise and set to a state
Get data from the second Promise and set to another state
APIservice.js
Child
Is it a good practice to setup all your API calls in a separate file? It's likely that in the future I will need to make different API calls, would you create multiple functions to handle that?
class APIservice {
_getStopPoint = (endpoint) => {
return new Promise(function(resolve, reject) {
fetch(endpoint)
.then((response) => response.json())
.then((data) => {
console.log("APIservice StopPoint", data)
resolve(data);
});
});
};
};
module.exports = new APIservice
App.js
Parent
As you can see, the way I setup the endpoint is lame. It's not ideal as the URL is the same. I want to structure something that can receive two variables and build the URL on the go. Something like https://api.tfl.gov.uk/Line/${routeid}/Arrivals/${stationid}.
If I manage that, how can I pass the API call to the APIservice having only one endpoint that dynamically will change based on the two variables it receives? I am not sure how to differentiate the call in the Promise.all having only "one" URL.
That brings me another issue. When setting the state in App.js, should I setState using the specifics array from data? Something like bus: data[0], tube: data[1]. Is this a good practice?
let APIservice = require('./APIservice')
let endpoint = 'https://api.tfl.gov.uk/Line/55/Arrivals/490004936E'
let endpoint1 = 'https://api.tfl.gov.uk/Line/Northern/Arrivals/940GZZLUODS'
let loadData = (endPoint) => {
// Multiple API calls
Promise.all([
APIservice._getStopPoint(endpoint),
APIservice._getStopPoint(endpoint1),
])
.then((data) => {
console.log("App.js", data)
})
.catch((error) => {
console.log(error)
})
}
export default class App extends Component {
componentWillMount() {
// URL fetch based on variables, not dynamic
loadData(endpoint)
loadData(endpoint1)
}
render() {
loadData("hello")
return (
<View style={styles.container}>
<Text>
Promise
</Text>
</View>
);
}
}
you can try this example
const callbackFn = (firstName, callback) => {
setTimeout(() => {
if (!firstName) return callback(new Error('no first name
passed in!'))
const fullName = `${firstName} Doe`
return callback(fullName)
}, 2000)
}
callbackFn('John', console.log)
callbackFn(null, console.log)
In my application code there are several places, where I have to connect to a DB and get some data.
For my unit tests (I'm using JestJS), I need to mock this out.
Let's assume this simple async function:
/getData.js
import DB from './lib/db'
export async function getData () {
const db = DB.getDB()
const Content = db.get('content')
const doc = await Content.findOne({ _id: id })
return doc
}
The DB connection is in a separate file:
/lib/db.js
import monk from 'monk'
var state = {
db: null
}
exports.connect = (options, done) => {
if (state.db) return done()
state.db = monk(
'mongodb://localhost:27017/db',
options
)
return state.db
}
exports.getDB = () => {
return state.db
}
You can see, I'll recieve the DB and get a collection. After this I will recieve the data.
My attempt for the mock so far:
/tests/getData.test.js
import { getData } from '../getData'
import DB from './lib/db'
describe('getData()', () => {
beforeEach(() => {
DB.getDB = jest.fn()
.mockImplementation(
() => ({
get: jest.fn(
() => ({
findOne: jest.fn(() => null)
})
)
})
)
})
test('should return null', () => {
const result = getData()
expect(result).toBeNull()
})
})
Maybe this is not the best way to do it...? I'm very happy for every improvement.
My question is where to put the DB mock as there are multiple tests and every test needs a different mock result for the findOne() call.
Maybe it is possible to create a function, which gets called with the needed parameter or something like that.
First I just want to note that testing this proof-of-concept function as-is appears low in value. There isn't really any of your code in there; it's all calls to the DB client. The test is basically verifying that, if you mock the DB client to return null, it returns null. So you're really just testing your mock.
However, it would be useful if your function transformed the data somehow before returning it. (Although in that case I would put the transform in its own function with its own tests, leaving us back where we started.)
So I'll suggest a solution that does what you asked, and then one that will hopefully improve your code.
Solution that doesn't require changing getData() - Not Recommended:
You can create a function that returns a mock that provides a findOne() that returns whatever you specify:
// ./db-test-utils
function makeMockGetDbWithFindOneThatReturns(returnValue) {
const findOne = jest.fn(() => Promise.resolve(returnValue));
return jest.fn(() => ({
get: () => ({ findOne })
}));
}
Then in your code file, call DB.getDB.mockImplementation in beforeEach or beforeAll above each test, passing in the desired return value, like this:
import DB from './db';
jest.mock('./db');
describe('testing getThingById()', () => {
beforeAll(() => {
DB.getDB.mockImplementation(makeMockGetDbWithFindOneThatReturns(null));
});
test('should return null', async () => {
const result = await getData();
expect(result).toBeNull();
});
});
Solution that makes testing much easier across your DB-related code
This question is really exciting, because it is a wonderful illustration of the value of having each function do only one thing!
getData appears to be very small - only 3 lines plus a return statement. So at first glance it doesn't seem to be doing too much.
However, this tiny function has very tight coupling with the internal structure of DB. It has dependency on:
DB - a singleton
DB.getDB()
DB.getDB().get()
DB.getDB().get().findOne()
This has some negative repercussions:
If DB ever changes its structure, which since it uses a 3rd party component, is possible, then every function you have that has these dependencies will break.
It's very hard to test!
The code isn't reusable. So every function that accesses the DB will need to call getDB() and db.get('collection'), resulting in repeated code.
Here's one way you could improve things, while making your test mocks much simpler.
Export db instead of DB
I could be wrong, but my guess is, every time you use DB, the first thing you'll do is call getDB(). But you only ever need to make that call once in your entire codebase. Instead of repeating that code everywhere, you can export db from ./lib/db.js instead of DB:
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export default dbInstance;
Alternatively, you could create the db instance in a startup function and then pass it in to a DataAccessLayer class, which would house all of your DB access calls. Again only calling getDB() once. That way you avoid the singleton, which makes testing easier because it allows dependency injection.
Add a helper function to get the DB collection
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export function getCollectionByName(collectionName){
return dbInstance.get(collectionName);
}
export default dbInstance;
This function is so trivial it might seem unnecessary. After all, it has the same number of lines as the code it replaces! But it removes the dependency on the structure of dbInstance (previously db) from calling code, while documenting what get() does (which is not obvious from its name).
Now your getData, which I'm renaming getDocById to reflect what it actually does, can look like this:
import { getCollectionByName } from './lib/db';
export async function getDocById(id) {
const collection = getCollectionByName('things');
const doc = await collection.findOne({ _id: id })
return doc;
}
Now you can mock getCollectionByName separately from DB:
// getData.test.js
import { getDocById } from '../getData'
import { getCollectionByName } from './lib/db'
jest.mock('./lib/db');
describe('testing getThingById()', () => {
beforeEach(() => {
getCollectionByName.mockImplementation(() => ({
findOne: jest.fn(() => Promise.resolve(null))
}));
});
test('should return null', async () => {
const result = await getDocById();
expect(result).toBeNull();
});
});
This is just one approach and it could be taken much further. For example we could export findOneDocById(collectionName, id) and/or findOneDoc(collectionName, searchObject) to make both our mock and calls to findOne() simpler.