launchdarkly / react-client-sdk

LaunchDarkly Client-side SDK for React.js
Other
86 stars 68 forks source link

Support a MockedProvider for Storybook/Testing #30

Open JaidenAshmore opened 4 years ago

JaidenAshmore commented 4 years ago

Is your feature request related to a problem? Please describe. I am working on a Typescript React App and currently use StorybookJS to be able to test my components over different use cases. These components are using the useFlags React Hook and therefore I need to provide a LaunchDarkly Provider to this. I don't want to actually communicate with a launch darkly server and instead just want to provide mock values for these flags in these storybooks.

Therefore I would like a way to provide mock Feature Flag values to my rendered components just like you use the MockedProvider in the Apollo GraphQL Client instead of using the withLDProvider.

As storybooks are not tests I cannot use Jest Mocks or Sinon.

Describe the solution you'd like The ability to do the following:

import { MockedProvider } from 'launchdarkly-react-client-sdk';

...

storiesOf('MyComponent', module)
    .add('will not render button when feature flag off', () => (
        <MockedProvider flags={{'test-flag': false}}>
            <MyComponent />
        </MockedProvider>
    ))
   .add('will render button when feature flag on', () => (
        <MockedProvider flags={{'test-flag': true}}>
            <MyComponent />
        </MockedProvider>
    ));

where my component is like:

const MyComponent = () => {
      const { testFlag } = useFlags();

      return (
            <>
                 {testFlag && <div>Flag On!</div>}
            </>
      );
}

Describe alternatives you've considered Maybe you are fine with me manually grabbing the Provider from inside the library? In which case I can pretty much do the equivalent in my components. E.g. currently I have something written like the following.

import React, { ReactNode } from 'react';

import { LDFlagSet } from 'launchdarkly-js-sdk-common';
import { Provider } from 'launchdarkly-react-client-sdk/lib/context';

type Props = {
    flags: LDFlagSet,
    children: ReactNode,
}

export const MockProvider = (props: Props) => (
    <Provider value={{ flags: props.flags }}>
        {props.children}
    </Provider>
);

If the above is a recommended way to do this mocking for our consumers and you don't want to provide a MockProvider as part of the library is there a place we can update documentation to recommend this approach for future developers? I struggled to find anything about this while googling.

yusinto commented 4 years ago

Thank you for filing this @JaidenAshmore! We are investigating ways to better support storybook use cases. One approach we are looking at is to officially support LaunchDarkly through storybook addons. We feel that this approach is best given storybook's rich ecosystem of addons and will be consistent with other products as well like apollo, figma, etc. Unfortunately there's no eta yet right now and it is still WIP. In the meantime you can proceed with the workaround you described above.

JaidenAshmore commented 4 years ago

Awesome, a Storybook addon is a much better approach. looking forward to seeing that!

eturino commented 3 years ago

Hi! We use Storybook but also regular end-to-end tests in which we'll need to mock LaunchDarkly provider.

The only issue with the workaround is having to import from launchdarkly-react-client-sdk/lib/context. I think if we could export the context as part of the package we could do this very easily and it will work without any problems (e.g. linting rules or things like snowpack that AFAIK has issues with this kind of imports).

I could open a PR with that and a MockProvider if you are ok with it.

yusinto commented 3 years ago

@JaidenAshmore thank you for your patience. We have just published jest-launchdarkly-mock which you can use with jest to test launchdarkly react components. This is intended for jest only, not storybook however.

We are currently still working on the storybook addon so we can't guarantee an ETA just yet.

BlueHotDog commented 3 years ago

hi guys, any update regarding the storybook component?

ghost commented 3 years ago

+1

ghost commented 3 years ago

In case you wanted to use a hacked version of it. Here is something that worked for me:

import { Provider, LDContext } from 'launchdarkly-react-client-sdk/lib/context';

const LDContextMock: LDContext = {
  flags: {
    yourFlagName: true,
  },
  ldClient: undefined,
};
const LDFlagContextProviderMock = ({ children }) => (
  <Provider value={LDContextMock}>
    {children}
  </Provider>
);

Include this in your story and wrap your LD dependent component with LDFlagContextProviderMock.

gardner commented 2 years ago

It would be easier to create a MockLDProvider in TypeScript if LDFlagSet was exported from launchdarkly-react-client-sdk/lib/context

jmhodges-color commented 1 year ago

Now that LDContext requires a LDFlagKeyMap, we've also had to write my own camel-casing function to turn LDFlagSet into a LDFlagKeyMap to add to example code above. (We also wrote a fake version of LDClient to take over the undefined in the example code.)

It'd be nice for there to be a defined way of getting a working React setup that didn't contact LaunchDarkly's servers so that local development we could try things out without stomping on each other. That'd mean something like a function that creates a working LDContext and a fake, no-requests LDClient from a Map<string, boolean> or LDFlagSet or whatever.

taschetto commented 1 year ago

I ended up using https://github.com/tdeekens/flopflip to wrap LaunchDarkly's React SDK. With it, I was able to use a memory/offline flags provider on tests and storybooks.

hon2a commented 1 year ago

This is blocking us from upgrading to launchdarkly-react-client-sdk@3.x, because version 3 no longer ships /lib/context and neither does it export the context or raw Provider in the main module. (We need to set the mock flags in our Cypress tests.) Reading the LD guides, launchdarkly-react-client-sdk doesn't seem to be supported for any alternative way of flag specification either, be it a fake source or loading flags from file.

It seems exceedingly simple to at least export the Context used by LD, even if just secretly (requiring us to add a TS module declaration augment before using it), so that we could mock the flags as needed. Alternatively, please let me know if there's any work-around that works with v3.

taschetto commented 1 year ago

This is blocking us from upgrading to launchdarkly-react-client-sdk@3.x, because version 3 no longer ships /lib/context and neither does it export the context or raw Provider in the main module. (We need to set the mock flags in our Cypress tests.) Reading the LD guides, launchdarkly-react-client-sdk doesn't seem to be supported for any alternative way of flag specification either, be it a fake source or loading flags from file.

It seems exceedingly simple to at least export the Context used by LD, even if just secretly (requiring us to add a TS module declaration augment before using it), so that we could mock the flags as needed. Alternatively, please let me know if there's any work-around that works with v3.

@hon2a My workaround is to use https://github.com/tdeekens/flopflip and toggle between a memory adapter (tests and development) and the LD adapter.

hon2a commented 1 year ago

@taschetto Thanks for the suggestion. I'd rather avoid adding another layer that I have no need for though. Still, it might come in handy eventually, if things get nowhere on this front.

kodai3 commented 11 months ago

Looks like some folks using my basic storybook plugin. I would love to hand over the maintenance as I can't prioritize keep up to date if it make sense

shleewhite commented 11 months ago

+1 That this is blocking us from upgrading to launchdarkly-react-client-sdk@3.x because we are using the unofficial add-on right now.

kenny-f commented 9 months ago

+1 for either exporting the Provider or some official way to mock in storybook.

bnussman-akamai commented 8 months ago

I've been able to work around this with bootstrap

import { LDProvider } from 'launchdarkly-react-client-sdk'

<LDProvider flags={options.flags ?? {}} options={{ bootstrap: options.flags }} clientSideID={''}>
  {children}
</LDProvider>

but it causes my console to be flooded with

[LaunchDarkly] No environment/client-side ID was specified. Please see https://docs.launchdarkly.com/sdk/client-side/javascript#initializing-the-client for instructions on SDK initialization.

It would be great if the Provider was exported like it was in the past!

faroceann commented 8 months ago

@yusinto mentioned in #234 that the recommended approach for now is to follow this example using storybooks mocks

frankalbenesius commented 7 months ago

Are there any updates on this front?

It's kinda confusing to me that being able to provided mocked values from a provider is not supported in any way.

kodai3 commented 7 months ago

They say they will create the package for almost four years but no single update shared to community. (and keep saying they will) I asked if I can trust the word in the PR comment but not even a reply, so I don't think we can rely on it.

eifr commented 6 months ago

I've been able to work around this with bootstrap

import { LDProvider } from 'launchdarkly-react-client-sdk'

<LDProvider flags={options.flags ?? {}} options={{ bootstrap: options.flags }} clientSideID={''}>
  {children}
</LDProvider>

but it causes my console to be flooded with

[LaunchDarkly] No environment/client-side ID was specified. Please see https://docs.launchdarkly.com/sdk/client-side/javascript#initializing-the-client for instructions on SDK initialization.

It would be great if the Provider was exported like it was in the past!

fixed the console span with new Logger:

export const noop = () => undefined;

export const withLaunchDarkly: Decorator = (Story, context) => {
  const params = context.parameters["launchdarkly"]?.flags ?? {};

  return (
    <LDProvider
      clientSideID=""
      flags={params}
      options={{
        bootstrap: params,
        logger: { debug: noop, error: noop, warn: noop, info: noop },
      }}
    >
      <Story />
    </LDProvider>
  );
};