As Im using Apollo Boost I can write GraphQL queries declaratively like so:
https://dev-blog.apollodata.com/zero-config-graphql-state-management-27b1f1b3c2c3
import { Query } from 'react-apollo';
const LOGGEDIN = gql`
mutation($value: Boolean) {
CurrentUserIsLoggedIn(value: $value) #client {
value
}
}
`;
const Test = () => (
<Query query={LOGGEDIN}>
{({ loading, error, data }) => {
if (loading) return <div>Loading...</div>;
if (error) return <div>Error :(</div>;
console.log(data.CurrentUserIsLoggedIn.value);
return <h2>Something</h2>;
}}
</Query>
);
Can I use a similar declarative approach to mutations?
#Evans, look at this, you'll find out not just the Mutation component 😉 https://github.com/apollographql/react-apollo/tree/v2.1.0-beta.3/test/client, even inside of example project from article which you've mentioned, it's what you are looking for!
You can see Declarative mutation in this repo..
https://github.com/apollographql/GitHunt-React
Related
I'm new to ember and trying to figure out how to unit test, using sinon, the sessionStorage based on url parameters when that page is visited. I've tried a few things but still can't get the desired result. It passes even if I change the 'sessionValue' without editing the query param.
Thank you in advance.
ember component
beforeModel(transition) {
//transition will contain an object containing a query parameter. '?userid=1234' and is set in the sessionStorage.
if(transition.queryparam.hasOwnProperty('userid')){
sessionStorage.setItem('user:id', transition.queryparam)
}
}
Ember test
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let store = {};
const mockLocalStorage = {
getItem: (key) => {
return key in store ? store[key] : null;
},
setItem: (key, value) => {
store[key] = `${value}`;
},
clear: () => {
store = {};
}
};
asserts.expect(1);
let spy = sinon.spy(sessionStorage, "setItem");
spy.calledWith(mockLocalStorage.setItem);
let stub = sinon.stub(sessionStorage, "getItem");
stub.calledWith(mockLocalStorage.getItem);
stub.returns(sessionValue);
await visit('/page?userid=1234');
mockLocalStorage.setItem(sessionKey, sessionValue);
assert.equal(mockLocalStorage.getItem(sessionKey), sessionValue, 'storage contains value');
})
Welcome to Ember!
There are many ways to test, and the below suggestion is one way (how I would approach interacting with the SessionStorage).
Instead of re-creating the SessionStorage API in your test, how do you feel about using a pre-made proxy around the Session Storage? (ie: "Don't mock what you don't own")
Using: https://github.com/CrowdStrike/ember-browser-services/#sessionstorage
Your app code would look like:
#service('browser/session-storage') sessionStorage;
beforeModel(transition) {
// ... details omitted ...
// note the addition of `this` -- the apis are entirely the same
// as SessionStorage
this.sessionStorage.setItem('user:id', ...)
}
then in your test:
module('Scenario Name', function (hooks) {
setupApplicationTest(hooks);
setupBrowserFakes(hooks, { sessionStorage: true });
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let sessionStorage = this.owner.lookup('browser/session-storage');
await visit('/page?userid=1234');
assert.equal(sessionStorage.getItem(sessionKey), '1234', 'storage contains value');
});
})
With this approach, sinon isn't even needed :)
I am looking to create a query-builder for my Amplify Datastore.
The function should process an an array of conditions, that need to be applied to the query and return the according Predicate.
This is easily done, if there is only one filter, but I would like to be able to process any amount of filters.
My goal is to be able to write the queries like so:
Datastore.query(Post, *queryBuilder(filters)*)
Where I can pass an array of filters with a filter looking like this:
filter = {
connector: 'or' |
property: rating
predicate: 'gt'
value: 4
}
and the query builder returns the Predicate in the below mentioned format.
I have tried to chain and return multiple functions in the query builder, but I was not able to figure out a pattern for how to create the correct predicate function.
For reference, this is how queries are built according to the docs: https://docs.amplify.aws/lib/datastore/data-access/q/platform/js#predicates
const posts = await DataStore.query(Post, c => c.rating("gt", 4));
and for multiple conditions:
const posts = await DataStore.query(Post, c =>
c.rating("gt", 4).status("eq", PostStatus.PUBLISHED)
);
Let's say we have the model:
type Post #model{
id: ID!
category: String
city: String
content: String
}
And we want to query & filter by city and category by a dynamic amount of variables. Then we can make a function as such on our script:
const fetchData = async props => {
/*
More configurable wrapper for Datastore.query calls
#param props: {model: Model, criteria: [{fieldId, predicate, value}]}.
*/
try {
let criteria;
if (props.criteria && typeof props.criteria === 'object') {
criteria = c => {
props.criteria.forEach(item => {
const predicate = item.predicate || 'eq';
c[item.fieldId](predicate, item.value);
});
return c;
};
} else {
criteria = props.criteria;
}
return await DataStore.query(props.model, criteria);
} catch (e) {
throw new Error(e);
}
}
So now if we want to execute this we can pass the parameters:
// where Post = models.Post
const myResult = fetchData({model: Post, criteria: [
{ fieldId: 'category',
predicate: 'eq',
value: 'news'
},
{
fieldId: 'city',
predicate: 'eq',
value: 'SomeCityName'
}]
})
Unfortunately I do not know of a way to also query linked relationships as you would using a direct graphQL api query while using DataStore and this method I presented only uses implicit AND between criteria.
I don't know if this has changed since you asked the question but, based on the documents, it looks like multiple conditions have an implicit and, but you can explicitly chain them with or/and/not:
const posts = await DataStore.query(Post, c => c.or(
c => c.rating("gt", 4).status("eq", PostStatus.PUBLISHED)
));
I'm running into an issue where running a query with the useQuery Apollo hook works fine, but if I use the useApolloClient hook to get the instance of ApolloClient and then call the client's query method, the call fails with the error Error: query option is required. You must specify your GraphQL document in the query option.
My code more or less looks like this:
import React from 'react'
import gql from 'graphql-tag'
import { useQuery, useApolloClient } from '#apollo/react-hooks'
const MyComponent = props => {
const QUERY = gql`
query MyPersonSearch ( $after: String, $filter: PersonFilter, $first: Int ) {
people: people ( after: $after, filter: $filter, first: $first ) {
totalCount
pageInfo {
endCursor
hasNextPage
}
edges {
node {
firstName
lastName
}
}
}
}
`
const queryVars = cursor => { after: cursor, ...otherQueryVars }
// This works
const { loading, error, data, fetchMore } = useQuery(
QUERY, { variables: queryVars( ... ) }
)
// This doesn't work
const client = useApolloClient()
const fetchPages = async () => {
const { data } = await client.query( QUERY, { variables: queryVars( ... ) } )
}
...
}
Any idea what's going on here? The error message is a little vague, but I'm assuming it means that client.query() expects a DocumentNode as its first parameter, and the return type of gql is any...but then again, if this were true, I would expect useQuery to fail too, since it also expects the query to be a DocumentNode.
Welp, turns out the syntax is not the same. client.query() expects a sole QueryOptions parameter, whereas useQuery can take the query as the first parameter, and an optional QueryOptions second parameter.
I'm performing a query to get PowerMeter details in which contains another type inside called Project. I write the query this way:
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
When I perform the query for the first time, project is successfully returned. The problem is that when I perform subsequent queries with the same parameters and default fetchPolicy (cache-first), project isn't returned anymore.
How may I solve this problem?
Also, I call readFragment to check how powerMeter is saved in the cache and the response shows that powerMeter has project saved.
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
Power Meter returned first time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
}
Fragment after calling power meter first time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Power Meter returned second time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"__typename":"PowerMeter"
}
}
Fragment after calling power meter second time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Edit 1: Fetching Query
The code below is how I'm fetching data. I'm using useApolloClient and not a query hook because I'm using AWS AppSync and it doesn't support query hook yet.
import { useApolloClient } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { useEffect, useState } from 'react';
export const getPowerMeterQuery = gql`
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
`;
export const useGetPowerMeter = (powerMeterId?: string) => {
const client = useApolloClient();
const [state, setState] = useState<{
loading: boolean;
powerMeter?: PowerMeter;
error?: string;
}>({
loading: true,
});
useEffect(() => {
if (!powerMeterId) {
return setState({ loading: false });
}
client
.query<GetPowerMeterQueryResponse, GetPowerMeterQueryVariables>({
query: getPowerMeterQuery,
variables: {
powerMeterId,
},
})
.then(({ data, errors }) => {
if (errors) {
setState({ loading: false, error: errors[0].message });
}
console.log(JSON.stringify(data));
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
console.log(JSON.stringify(frag));
setState({
loading: false,
powerMeter: data.powerMeter,
});
})
.catch(err => setState({ loading: false, error: err.message }));
}, [powerMeterId]);
return state;
};
Edit 2: Fetching Policy Details
When I use fetchPolice equals cache-first or network-only, the error persists. When I use no-cache, I don't get the error.
I think this might have been the solution:
https://github.com/apollographql/apollo-client/issues/7050
Probably way too late, but it could help people coming to this issue in the future.
When using apollo client's InMemoryCache it seems you need to provide a list of possible types so the fragment matching can be done correctly when using the InMemoryCache.
You can do that manually when having few union types and a pretty stable API which doesn't change very often.
Or you automatically generate these types into a json file, which you can use directly in the InMemoryCache's possibleTypes config directly.
Visit this link to the official docs to find out how to do it.
Cheers.
I'm trying to write a basic unit test to work on the function below, but can't get it to work. How do I test that something like a proper npm-express response is returned?
I already looked at Using Sinon to stub chained Mongoose calls, https://codeutopia.net/blog/2016/06/10/mongoose-models-and-unit-tests-the-definitive-guide/, and Unit Test with Mongoose, but still can't figure it out. My current best guess, and the resulting error, is below the function to be tested. If possible, I don't want to use anything but Mocha, Sinon, and Chai.expect (i.e. not sinon-mongoose, chai-as-expected, etc.). Any other advice, like what else I can/should test here, is welcome. Thank you!
The function to be tested:
function testGetOneProfile(user_id, res) {
Profiles
.findOne(user_id)
.exec()
.then( (profile) => {
let name = profile.user_name,
skills = profile.skills.join('\n'),
data = { 'name': name, 'skills': skills };
return res
.status(200)
.send(data);
})
.catch( (err) => console.log('Error:', err));
}
My current best-guess unit test:
const mongoose = require('mongoose'),
sinon = require('sinon'),
chai = require('chai'),
expect = chai.expect,
Profile = require('../models/profileModel'),
foo = require('../bin/foo');
mongoose.Promise = global.Promise;
describe('testGetOneProfile', function() {
beforeEach( function() {
sinon.stub(Profile, 'findOne');
});
afterEach( function() {
Profile.findOne.restore();
});
it('should send a response', function() {
let mock_user_id = 'U5YEHNYBS';
let expectedModel = {
user_id: 'U5YEHNYBS',
user_name: 'gus',
skills: [ 'JavaScript', 'Node.js', 'Java', 'Fitness', 'Riding', 'backend']
};
let expectedResponse = {
'name': 'gus',
'skills': 'JavaScript, Node.js, Java, Fitness, Riding, backend'
};
let res = {
send: sinon.stub(),
status: sinon.stub()
};
sinon.stub(mongoose.Query.prototype, 'exec').yields(null, expectedResponse);
Profile.findOne.returns(expectedModel);
foo.testGetOneProfile(mock_user_id, res);
sinon.assert.calledWith(res.send, expectedResponse);
});
});
The test message:
1) testGetOneProfile should send a response:
TypeError: Profiles.findOne(...).exec is not a function
at Object.testGetOneProfile (bin\foo.js:187:10)
at Context.<anonymous> (test\foo.test.js:99:12)
This is a bit of a tricky scenario. The problem here is that the findOne stub in your test returns the model object - instead, it needs to return an object which contains a property exec which in turn is a promise-returning function that finally resolves into the model value... yeah, as mentioned, it's a bit tricky :)
Something like this:
const findOneResult = {
exec: sinon.stub().resolves(expectedModel)
}
Profile.findOne.returns(findOneResult);
You also need to have the status function on the response object return an object containing a send function
//if we set up the stub to return the res object
//it returns the necessary func
res.status.returns(res);
I think you shouldn't need to change anything else in the test and it might work like that. Note that you sinon 2.0 or newer for the resolves function to exist on the stub (or you can use sinon-as-promised with sinon 1.x)
This post goes into a bit more detail on how you can deal with complex objects like that:
https://codeutopia.net/blog/2016/05/23/sinon-js-quick-tip-how-to-stubmock-complex-objects-such-as-dom-objects/