Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to mock server so that feathers-pinia behaves normally with mocked responses and delays ? #140

Open
barnacker opened this issue Aug 11, 2023 · 4 comments
Labels
documentation Improvements or additions to documentation

Comments

@barnacker
Copy link

Hi! I want to mock a feathers socket server. I was thiking of doing it on the feathers-client / socket.io transport declarations maybe and completely skip network and sockets but yet getting responses as if a real feathers servers replied.

It's really important that I do not use a real server and real neworking for my UI Prototype / POC, all is to be mocked even delays of responses etc...

I tried mock-socket and replaced the socket.io import in the feathers-client file... I can intercept the create signal for example and I see Pinia is pending for creation but no matter what I tried I emit back, feathers-pinia doesnt see it ... I might just not reply propely.

Any ideas?
Here is what we did... it almost works!!! :-|

Code on the UI:

<script lang="ts" setup>
  import { api } from '../store/index'

  const createMock = () => {
    const mockService = api.service('mocks');
    const aMock = mockService.new({ version: 'jean' });
    aMock.createInStore();
    aMock.save();
  }

</script>

Example feathers-client.ts file:

import feathers from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio-client'
import auth from '@feathersjs/authentication-client'
// import io from 'socket.io-client'
import { SocketIO, Server } from 'mock-socket';

const fakeURL = 'ws://localhost:3030';

const mockServer = new Server(fakeURL);

mockServer.on('connection', socket => {
  socket.on('create', function () {
    console.log('mockServer', arguments)
    // This works, we intercepted the create event and It logs this in the console: 
    // {
    //   "0": "mocks",
    //   "1": {
    //       "version": "jean"
    //   },
    //   "2": {}
    // }

    socket.emit('create',{id:11, version: 'joeBin'});
    // We tried emit all kinds of answers but I guess it's not what feathers-pinia is listening for... or mock-socket doesn't work as feathers needs it...
  });
})

const socket = new SocketIO(fakeURL)

// This variable name becomes the alias for this server.
export const feathersClient = feathers()
  .configure(socketio(socket))
@barnacker
Copy link
Author

We also found this possible way:
https://canjs.com/doc/can-fixture-socket.html#UsewithFeathersJS

@marshallswain
Copy link
Owner

@barnacker, could you share the code that you used to set this up? I'd like to give it its own section in the "Common Patterns" page. I would really appreciate it.

@barnacker
Copy link
Author

barnacker commented Aug 22, 2023

Yes! Roughly to make it easier we created this class (see at the bottom) so we can register mocked services with mocked data and we integrated it into our feathers-client.ts like below.

Then you can create the pinia store as usual and it will react as usual, we managed to override the authentication service too so its greate for fast login :)

Here below is our fancier latest code, it's designed to avoid multipicating listeners ( server.on('create',... ) on the socket server... but if you prefer a funcionning dirtyier simpler exemple here is one you can also use:
https://github.com/bewave-io/boilerplate_socketio_mockup/tree/feature/test-can-fixture-socket
Direct link:
https://github.com/bewave-io/boilerplate_socketio_mockup/blob/feature/test-can-fixture-socket/frontend/src/feathers.ts

// src/feathers-client.ts
import feathers from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio-client'
import io from 'socket.io-client'
import fixtureSocket from "can-fixture-socket";


// Create a mock server that intercepts socket.io:
const mockServer = new fixtureSocket.Server(io);
const mockServices = new MockFeathersService(mockServer);

mockServices.addService('hello');
mockServices.addService('world');
mockServices.addService('health-check');
mockServices.addService('i18n');

// Client. Create socket.io connection:
const socket = io('http://localhost:3030', { transports: ['websocket'] });

// This variable name becomes the alias for this server.
export const feathersClient = feathers()
  .configure(socketio(socket));

mockFeathersService.ts Class

// src/mockFeathersService.ts
export default class MockFeathersService {
  constructor(mockServer) {
    this.servicesDic = {};
    // eslint-disable-next-line no-promise-executor-return
    const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

    mockServer.on('find', async (serviceName, query, acknowledgementFunction) => {
      if (serviceName in this.servicesDic) {
        console.log(`Simulating find ${serviceName}`, serviceName, query, acknowledgementFunction);
        await sleep(this.servicesDic[serviceName].delay);
        const { dataSet } = this.servicesDic[serviceName];
        console.log('dataSet find', dataSet);
        acknowledgementFunction(null, {
          total: dataSet.length, limit: 9999, skip: 0, data: dataSet.data,
        });
      } else {
        console.error(
          `${serviceName} : failed simulating find for undefined mock service`,
          serviceName,
          query,
          acknowledgementFunction,
        );
      }
    });

    mockServer.on('get', async (serviceName, id, noUse, acknowledgementFunction) => {
      if (serviceName in this.servicesDic) {
        console.log(`Simulating get ${serviceName}`, serviceName, id, noUse, acknowledgementFunction);
        await sleep(this.servicesDic[serviceName].delay);
        const { dataSet } = this.servicesDic[serviceName];
        const record = dataSet.data.find((elt) => elt.id === id);
        console.log(record);
        acknowledgementFunction(null, record);
      } else {
        console.error(
          `${serviceName} : failed simulating get for undefined mock service`,
          serviceName,
          id,
          noUse,
          acknowledgementFunction,
        );
      }
    });

    mockServer.on('create', async (serviceName, data, noUse, acknowledgementFunction) => {
      if (serviceName in this.servicesDic) {
        console.log(`Simulating create ${serviceName}`, serviceName, data, noUse, acknowledgementFunction);
        await sleep(this.servicesDic[serviceName].delay);
        const { dataSet } = this.servicesDic[serviceName];
        // Call the mockSet "create" but we shoud just echo the data if there is no function defined in the mockSet
        acknowledgementFunction(null, dataSet.create(data));
      } else {
        console.error(
          `${serviceName} : failed simulating create for undefined mock service ${serviceName}`,
          serviceName,
          data,
          noUse,
          acknowledgementFunction,
        );
      }
    });

    mockServer.on('patch', async (serviceName, id, data, noUse, acknowledgementFunction) => {
      if (serviceName in this.servicesDic) {
        console.log(`Simulating patch %c${serviceName} :`, 'color: red', serviceName, data, noUse, acknowledgementFunction);
        await sleep(this.servicesDic[serviceName].delay);
        acknowledgementFunction(null, data);
      } else {
        console.error(
          `${serviceName} : failed simulating patch for undefined mock service ${serviceName}`,
          serviceName,
          id,
          data,
          noUse,
          acknowledgementFunction,
        );
      }
    });

    mockServer.on('remove', async (serviceName, id, noUse, acknowledgementFunction) => {
      if (serviceName in this.servicesDic) {
        console.log(`Simulating remove %c${serviceName} :`, 'color: red', serviceName, id, noUse, acknowledgementFunction);
        await sleep(this.servicesDic[serviceName].delay);
        acknowledgementFunction(null, { id, deleted: true });
      } else {
        console.error(
          `${serviceName} : failed simulating remove for undefined mock service ${serviceName}`,
          serviceName,
          id,
          noUse,
          acknowledgementFunction,
        );
      }
    });
  }

  addService(mockName, importName = mockName, delay = 1000) {
   // We list all mockSet files
    const dataSetFiles = import.meta.glob(
      '@/features/**/*.mockSet.ts',
      { eager: true },
    );

    // We only import the data if the service name you provided matches
    // SERVICENAME.mockSet.ts

    // The following code, you might find weird, so you can probaly scrap all that and simplify for the common patterns....
    // It's for our secific project where we aggreggate the data if we found the same "mockSet" name in multiple subfolders 
    // because our scaffolding is by "features" we just fill one generic pinia store named "mocks" that contains data for 
    // multiple views and we just then filter in the store what subset we want in the find query... not a common pattern haha

    // still below this code will be an example "mockSet" as you might like the pattern that they are designed to provide data 
    // as well as their own custom behaviors such as a "create" funtion that generates an id to mock the server behavior

    // Import and Aggreggate data
    let dataSet = { data: [] };
    Object.keys(dataSetFiles).forEach((path) => {
      if (path.includes(`${importName}.mockSet.ts`)) {
        const dataObject = dataSetFiles[path];
        dataSet = {
          ...dataSet,
          ...dataObject.default,
          data: [...dataSet.data, ...dataObject.default.data],
        };
      }
    });

    console.log(`dataSet for service ${importName} = `, dataSet);
    this.servicesDic[mockName] = { dataSet, delay };
  }
}

Example mocked data and create function

// src/features/dogs/mocks.mockSet.ts
export default {
  data: [
    { id: 1, name: 'john10' },
    { id: 2, name: 'john20' },
    { id: 3, name: 'john30' },
    { id: 4, name: 'john40' },
    { id: 5, name: 'john50' },
    { id: 6, name: 'john60' },
    { id: 7, name: 'john70' },
  ],
  create: (data) => {
    return { ...data, id: Math.floor(Math.random() * 100) };
  },
};

@barnacker
Copy link
Author

barnacker commented Aug 22, 2023

I hope this inspires you, our next goal I think is to allow for hybrid project with real and mocked services accessible during development :)

@marshallswain marshallswain added the documentation Improvements or additions to documentation label Mar 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants