I have set a cookie with nookies which store the values of all the products selected by user.
I want to fetch the cookie in server side using getServerSideProps and pass the value as props. I have to display the value of cookie on all pages.
When I tried getServerSideProps in _app.js. It did not worked and it did not even run the code.
Is there any way to do it?
As of now, there isn't a built-in way to do it, so I've resorted to doing the following.
First, I created a file that holds the getServerSideProps function I want to run on every page:
// lib/serverProps.js
export default async function getServerSideProps(ctx) {
// do something
return {
// data
};
}
Then in every page (yes, every, I can't find a workaround; it might even be helpful if you don't need the code to execute on server pages), do:
import getServerSideProps from "../lib/serverProps";
// other stuff...
export { getServerSideProps };
or
// other stuff...
export { default as getServerSideProps } from "../lib/serverProps";
If you want to add other code to run inside getServerSideProps for a specific page, you could do something along the lines...
import serverProps from "../lib/serverProps";
// other stuff...
export async function getServerSideProps(ctx) {
// do custom page stuff...
return {
...await serverProps(ctx),
...{
// pretend this is what you put inside
// the return block regularly, e.g.
props: { junk: 347 }
}
};
}
getServerSideProps does not work in _app.js. see docs.
you could use the older getInitialProps in your custom app component but then the automatic static optimisation is disabled, which is something Next.js bets on heavily.
it might be worth digging into your cookie use case and figure out if you really need to read it on the server side.
For those wanting to share state received from a page's getServerSideProps function to global components in pages/_app.tsx, I've pieced this solution together.
Create a shared getServerSideProps function to include on all pages
Create a shared useSetUserStorage custom hook to include on all pages
Listen for localStorage changes with custom event listener in global component (e.g. GlobalNav)
It's a work around, but is working for me so far (note that it includes some specifics to my use of getServerSideProps function).
It's a fair amount of code but hopefully this helps someone:
// src/pages/_app.tsx
import type { AppProps } from "next/app";
import GlobalNav from "../components/GlobalNav";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<GlobalNav /> // <— your global component
<Component {...pageProps} />
</>
);
}
export default MyApp;
// src/utils/getServerSideProps.ts
import { ppinit, ppsession, sess } from "../authMiddleware";
import nc from "next-connect";
import { NextApiRequest, NextApiResponse } from "next";
import { User } from "../types/types";
interface ExtendedReq extends NextApiRequest {
user: User;
}
interface ServerProps {
req: ExtendedReq;
res: NextApiResponse;
}
interface ServerPropsReturn {
user?: User;
}
//
// Here we use middleware to augment the `req` with the user from passport.js
// to pass to the page
// src: https://github.com/hoangvvo/next-connect/tree/21c9c73fe3746e66033fd51e2aa01d479e267ad6#runreq-res
//
const getServerSideProps = async ({ req, res }: ServerProps) => {
// ADD YOUR CUSTOM `getServerSideProps` code here
const middleware = nc()
.use(sess, ppinit, ppsession)
.get((req: Express.Request, res: NextApiResponse, next) => {
next();
});
try {
await middleware.run(req, res);
} catch (e) {
// handle the error
}
const props: ServerPropsReturn = {};
if (req.user) props.user = req.user;
return { props };
};
export interface Props {
user?: User;
}
export default getServerSideProps;
// src/hooks.ts
import { useEffect } from "react";
import { User } from "./types/types";
export const useSetUserStorage = (user?: User) => {
useEffect(() => {
if (user) {
localStorage.setItem("user", JSON.stringify(user));
} else {
localStorage.removeItem("user");
}
// whether setting or removing the user, dispatch event so that `GlobalNav`
// component (which is above the page implementing this hook in the
// component hierarchy) can be updated to display the user status. we
// can't use `window.addEventListener('storage', handler)` as this only
// works for listening for events from other pages
document.dispatchEvent(new Event("localStorageUserUpdated"));
});
return null;
};
// src/pages/index.tsx (or any page)
import { useSetUserStorage } from "../hooks";
import { Props } from "../utils/getServerSideProps";
export { default as getServerSideProps } from "../utils/getServerSideProps";
export default function Home({ user }: Props) {
useSetUserStorage(user);
return (
<>
<h1>Welcome to my app {user?.username}</h1>
</>
);
}
// src/components/GlobalNav.ts (or another global component)
import { useEffect, useState, MouseEvent } from "react";
import { User } from "../types/types";
const GlobalNav = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const handleUserLocalStorage = () => {
const userString = localStorage.getItem("user");
try {
if (userString) {
setUser(JSON.parse(userString));
} else {
setUser(null);
}
} catch (e) {
// handle parse error
}
};
handleUserLocalStorage();
// this component (`GlobalNav`) lives at the application level, above the
// pages, but the pages receive the user object from `getServerSideProps`,
// so this listener listens for when a page tells us the user object has
// changed so we can update the `user` state here.
document.addEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
false,
);
return () => {
// remove listener if component unmounts
document.removeEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
);
};
}, []);
return (
<div>
{user?.username}
</div>
);
};
export default GlobalNav;
I used a slightly different technique. Every page, in my case, has its own getServerSideProps and I was looking for a more functional approach. Also I'm using GraphQL, but the idea is the same no matter which data fetching API you choose. A regular getServerSideProps would look like this -
export const getServerSideProps: GetServerSideProps = async (context) => {
const { slug } = context.query
const { data: profile } = await client.query({ query: GetProfileDocument, variables: { slug } })
return {
props: {
...(await getSelf(context)),
profile: profile?.GetProfile[0],
},
}
}
In the props you can see the await statement, which is called in all pages. And in the few cases I don't need it, it's gone. This is what getSelf looks like -
const getSelf = async (context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => {
const session = await getSession(context)
let self = null
if (session) {
const { data } = await client.query({
query: GetProfileDocument,
variables: { secret: session?.secretSauce as string },
})
self = data.GetProfile[0]
}
return { self, sessionData: session }
}
Hope it helped.
Related
I am trying to pass data from the server to the client to load my app faster and prevent multiple calls to the database.
Via Fetch
SvelteKit is made to do this via the fetch function. This is great if you have an endpoint that allows for custom fetch. But what if you don't?
Firebase is a perfect example of not having a custom fetch function.
Cookies
I would think I could use cookies, but when I set the cookie, it just prints 'undefined' and never gets set.
<script lang="ts" context="module">
import Cookies from 'js-cookie';
import { browser } from '$app/env';
import { getResources } from '../modules/resource';
export async function load() {
if (browser) {
// working code would use JSON.parse
const c = Cookies.get('r');
return {
props: {
resources: c
}
};
} else {
// server
const r = await getResources();
// working code would use JSON.stringify
Cookies.set('resources', r);
// no cookies were set?
console.log(Cookies.get());
return {
props: {
resources: r
}
};
}
}
</script>
So my code loads correctly, then dissapears when the browser load function is loaded...
Surely there is a functioning way to do this?
J
So it seems the official answer by Rich Harris is to use and a rest api endpoint AND fetch.
routes/something.ts
import { getFirebaseDoc } from "../modules/posts";
export async function get() {
return {
body: await getFirebaseDoc()
};
}
routes/content.svelte
export async function load({ fetch }) {
const res = await fetch('/resources');
if (res.ok) {
return {
props: { resources: await res.json() }
};
}
return {
status: res.status,
error: new Error()
};
}
This seems extraneous and problematic as I speak of here, but it also seems like the only way.
J
You need to use a handler that injects the cookie into the server response (because load functions do not expose the request or headers to the browser, they are just used for loading props I believe). Example here: https://github.com/sveltejs/kit/blob/59358960ff2c32d714c47957a2350f459b9ccba8/packages/kit/test/apps/basics/src/hooks.js#L42
https://kit.svelte.dev/docs/hooks#handle
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.request.headers.get('cookie'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
return response;
}
FYI: This functionality was only added 11 days ago in #sveltejs/kit#1.0.0-next.267: https://github.com/sveltejs/kit/pull/3631
No need to use fetch!
You can get the data however you like!
<script context="module">
import db from '$/firebaseConfig'
export async function load() {
const eventref = db.ref('cats/whiskers');
const snapshot = await eventref.once('value');
const res = snapshot.val();
return { props: { myData: res.data } } // return data under `props` key will be passed to component
}
</script>
<script>
export let myData //data gets injected into your component
</script>
<pre>{JSON.stringify(myData, null, 4)}</pre>
Here's a quick demo on how to fetch data using axios, same principle applies for firebase: https://stackblitz.com/edit/sveltejs-kit-template-default-bpr1uq?file=src/routes/index.svelte
If you want to only load data on the server you should use an "endpoint" (https://kit.svelte.dev/docs/routing#endpoints)
My solution might solve it especially for those who work with (e.g: laravel_session), actually in your case if you want to retain the cookie data when loading on each endpoint.
What you should gonna do is to create an interface to pass the event on every api() call
interface ApiParams {
method: string;
event: RequestEvent<Record<string, string>>;
resource?: string;
data?: Record<string, unknown>;
}
Now we need to modify the default sveltekit api(), provide the whole event.
// localhost:3000/users
export const get: RequestHandler = async (event) => {
const response = await api({method: 'get', resource: 'users', event});
// ...
});
Inside your api() function, set your event.locals but make sure to update your app.d.ts
// app.d.ts
declare namespace App {
interface Locals {
r: string;
}
//...
}
// api.ts
export async function api(params: ApiParams) {
// ...
params.event.locals.r = response.headers.get('r')
});
Lastly, update your hooks.ts
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const response = await resolve(event);
if (!cookies.whatevercookie && event.locals.r) {
response.headers.set(
'set-cookie',
cookie.serialize('whatevercookie', event.locals.r, {
path: '/',
httpOnly: true
})
);
}
return response;
});
Refer to my project:
hooks.ts
app.d.ts
_api.ts
index.ts
I'm working with my own api and I can see it work if I use #urql/svelte but since we're using Apollo with React on most of our projects, I would like to see the differences between frameworks using the same dependency.
My lib/client.js looks like this:
import { ApolloClient, HttpLink, InMemoryCache } from '#apollo/client/core';
function createApolloClient() {
const httpLink = new HttpLink({
uri: 'MY_API'
});
const cache = new InMemoryCache();
const client = new ApolloClient({
httpLink,
cache
});
return client;
}
const client = new createApolloClient();
export default client;
My index.svelte is looking like this
<script>
import { setClient, mutation } from 'svelte-apollo';
import { gql } from '#apollo/client/core';
import { browser } from '$app/env';
import { onMount } from 'svelte';
import client from '../lib/client';
const email = 'AN_EMAIL';
const password = 'A_PASSWORD';
let userName;
let isLoggedIn = false;
setClient(client);
const SIGN_IN = gql`
mutation ($email: String!, $password: String!) {
userSignIn(email: $email, password: $password) {
email
id
isEnabled
name
surname
userType
}
}
`;
const signInMutation = mutation(SIGN_IN);
async function signInAction() {
await try {
signInMutation({ variables: { email, password } }).then((result) => console.log(result));
} catch (error) {
console.log(error);
}
}
const isUserLoggedIn = () => {
if (browser && localStorage.getItem('isLoggedIn') && localStorage.getItem('userName')) {
isLoggedIn = true;
userName = localStorage.getItem('userName');
}
};
onMount(() => {
isUserLoggedIn();
});
</script>
<button on:click={signInAction}>Trigger</button>
{#if isLoggedIn}
<h1>Welcome {userName}</h1>
{/if}
I honestly can't figure out what I'm missing with the Apollo setup.
I have no errors on my console and my network doesn't show anything when I click the button. The UI seems to work fine with the urql setup.
Could someone point me in the right direction? Thank you!
You have an issue in your client setup:
// ...
// const client = new createApolloClient(); // wrong use of 'new' keyword, createApolloClient() is a regular function, not a class constructor!
const client = createApolloClient();
// ...
As stated in my comment, you also have an issue in your signInAction function definition. You need to settle for one syntax:
// async/await
async function signInAction() {
try {
const result = await signInMutation({ variables: { email, password } });
console.log(result);
} catch (error) {
console.log(error);
}
}
// then/catch
function signInAction() {
signInMutation({ variables: { email, password } })
.then((result) => console.log(result))
.catch((error) => console.log(error));
}
Off-topic and opinionated: svelte-apollo radically differs from the react apollo client, is not an 'official' apollo client, and has not been updated for the past year+. You will be much better off going back to #urql/svelte.
I was hoping to get information to populate through SSR before the page loads. I've been following this example https://github.com/zeit/next.js/tree/canary/examples/with-apollo-auth/pages but been noticing the apolloClient doesn't exist within getInitialProps.
My withAuth.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { ApolloProvider } from 'react-apollo';
import PropTypes from 'prop-types';
import Head from 'next/head';
import Cookies from 'js-cookie';
import fetch from 'isomorphic-unfetch';
export const withApollo = (PageComponent, { ssr = true } = {}) => {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState, { getToken });
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
if (process.env.NODE_ENV !== 'production') {
// Find correct display name
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
// Warn if old way of installing apollo is used
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.');
}
// Set correct display name for devtools
WithApollo.displayName = `withApollo(${displayName})`;
// Add some prop types
WithApollo.propTypes = {
// Used for getDataFromTree rendering
apolloClient: PropTypes.object,
// Used for client/server rendering
apolloState: PropTypes.object
};
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
console.log(AppTree);
// Run all GraphQL queries in the component tree
// and extract the resulting data
const apolloClient = (ctx.apolloClient = initApolloClient(
{},
{
getToken: () => getToken(ctx.req)
}
));
const pageProps = PageComponent.getInitialProps ? await PageComponent.getInitialProps(ctx) : {};
// Only on the server
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return {};
}
if (ssr) {
try {
// Run all GraphQL queries
console.log('trying');
const { getDataFromTree } = await import('#apollo/react-ssr');
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState
};
};
}
return WithApollo;
};
let apolloClient = null;
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
*/
const initApolloClient = (...args) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(...args);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(...args);
}
return apolloClient;
};
const createApolloClient = (initialState = {}, { getToken }) => {
let fetchOptions = {};
const HTTP_ENDPOINT = 'http://localhost:4000/api';
const httpLink = createHttpLink({
uri: HTTP_ENDPOINT,
credentials: 'same-origin',
fetch,
fetchOptions
});
const authLink = setContext((request, { headers }) => {
const token = getToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
return new ApolloClient({
ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
link: authLink.concat(httpLink),
cache: new InMemoryCache().restore(initialState)
});
};
const getToken = () => {
return Cookies.get('token');
};
I'm using it as a HOC in my _app.js file and been trying to get access to the apolloClient in my Signin component hoping to do a check if a person is logged in, in order to redirect them (also would like to know in order to make the navbar dynamic)
Thank you for the help on this one
Try the following code and now you should be able to access apolloClient within getInitialProps.
const apolloClient = (ctx.ctx.apolloClient = initApolloClient({}, {
getToken: () => getToken(ctx.req)}));
I think you just missed one thing i.e. to return the apolloClient while returning the PageProps and ApolloCache when SSR is true.
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
// To get access to client while in SSR
apolloClient
};
I'm curious about how I could implement this, I'd like to not hit this API every time the page loads on the route, but would rather start the call on an action (I suppose this action could go anywhere, but it's currently in a component). I'm getting a server response, but having trouble getting this data inside my component/template. Any ideas? Ignore my self.set property if I'm on the wrong track there....Code below..Thanks!
import Component from '#ember/component';
export default Component.extend({
res: null,
actions: {
searchFlight(term) {
let self = this;
let url = `https://test.api.amadeus.com/v1/shopping/flight-offers?origin=PAR&destination=LON&departureDate=2018-09-25&returnDate=2018-09-28&adults=1&travelClass=BUSINESS&nonStop=true&max=2`;
return fetch(url, {
headers: {
'Content-Type': 'application/vnd.amadeus+json',
'Authorization':'Bearer JO5Wxxxxxxxxx'
}
}).then(function(response) {
self.set('res', response.json());
return response.json();
});
}
}
});
Solved below...
import Component from '#ember/component';
export default Component.extend({
flightResults: null,
actions: {
searchFlight(term) {
let self = this;
let url = `https://test.api.amadeus.com/v1/shopping/flight-offers?origin=PAR&destination=LON&departureDate=2018-09-25&returnDate=2018-09-28&adults=1&travelClass=BUSINESS&nonStop=true&max=2`;
return fetch(url, {
headers: {
'Content-Type': 'application/vnd.amadeus+json',
'Authorization':'Bearer xxxxxxxxxxxxxx'
}
}).then(function(response) {
return response.json();
}).then(flightResults => {
this.set('flightResults', flightResults);
});
}
}
});
You might find ember-concurrency to be useful in this situation. See the example of "Type-ahead search", modified for your example:
const DEBOUNCE_MS = 250;
export default Controller.extend({
flightResults: null;
actions: {
searchFlight(term) {
this.set('flightResults', this.searchRepo(term));
}
},
searchRepo: task(function * (term) {
if (isBlank(term)) { return []; }
// Pause here for DEBOUNCE_MS milliseconds. Because this
// task is `restartable`, if the user starts typing again,
// the current search will be canceled at this point and
// start over from the beginning. This is the
// ember-concurrency way of debouncing a task.
yield timeout(DEBOUNCE_MS);
let url = `https://test.api.amadeus.com/v1/shopping/flight-offers?origin=PAR&destination=LON&departureDate=2018-09-25&returnDate=2018-09-28&adults=1&travelClass=BUSINESS&nonStop=true&max=2`;
// We yield an AJAX request and wait for it to complete. If the task
// is restarted before this request completes, the XHR request
// is aborted (open the inspector and see for yourself :)
let json = yield this.get('getJSON').perform(url);
return json;
}).restartable(),
getJSON: task(function * (url) {
let xhr;
try {
xhr = $.getJSON(url);
let result = yield xhr.promise();
return result;
// NOTE: could also write this as
// return yield xhr;
//
// either way, the important thing is to yield before returning
// so that the `finally` block doesn't run until after the
// promise resolves (or the task is canceled).
} finally {
xhr.abort();
}
}),
});
Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties