mthuret / storybook-addon-specifications

:book: Write tests next to your stories and display their results inside storybook interface
457 stars 54 forks source link

Add Jest Snapshots #4

Closed luisherranz closed 8 years ago

luisherranz commented 8 years ago

I love the idea to write tests based on your stories!

Facebook released a new functionality to Jest a couple of weeks ago, called snapshots: https://facebook.github.io/jest/blog/2016/07/27/jest-14.html

What do you think about integrating jest snapshots with your addon? Would that be possible?

Imagine if a story:

stories.add('Hello World Button', () => (
  <button onClick={action('Hello World')}>
    Hello World
  </button>
));

could be automatically converted to a snapshot test:

test('Hello World Button', () => {
  const tree = renderer.create(
    <button onClick={action('Hello World')}>
      Hello World
    </button>
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

Would that be neat?

mthuret commented 8 years ago

Hi,

Actually, I saw two things here :

  1. Automatically perform jest snapshot-testing for every storybook stories.
  2. Display snapshots results inside a dedicated panel in storybook.

The first one can already be achieved. If you modify the facade.js used for mocking storybook (and that is read by Jest) with this one, it will perform a snapshot for every existing stories. (and you can use the -u option to reset them all). Note that I use enzyme to mount the component because of wierd behavior when mixing enzyme and react-test-renderer, but if you are not using enzyme, I assume, react-test-renderer can be used.

import {mount} from "enzyme";

export const storiesOf = function storiesOf() {
  var api = {};
  var story;
  api.add = (name, func)=> {
    story = func();
    snapshot(name, story);
    return api;
  };
  api.addWithInfo = (name, func)=> {
    story = func();
    snapshot(name, story);
    return api;
  };
  return api;
};
export const action = () => {};

export const linkTo = () => {};

export const specs = (spec) => {
  spec();
};

export const snapshot = (name, story) => {
  describe(name + ' snapshot', function () {
    it(name, function () {
      const tree = mount(story).html();
      expect(tree).toMatchSnapshot();
    });
  });
};

export const describe = jasmine.currentEnv_.describe;
export const it = jasmine.currentEnv_.it;

Maybe you can try it and tell what you think about it.

Regarding The second one, display snapshot results in storybook, the storybook team is already thinking to add this kind of feature. Well I don't know yet how it would be done, but it will probably came in the near futur.

arunoda commented 8 years ago

@luisherranz We are trying to implement this directly into storybook. Not sure it's an addon or something directly integrated to Storybook.

I'm considering to do it without the use of JEST. But simply using react-test-renderer. I think that's another type of testing method. May be not we should do it with another project. But not with this.

luisherranz commented 8 years ago

Awesome @arunoda. Always one step ahead 👍

luisherranz commented 8 years ago

@mthuret many thanks to you too. I guess we should wait until the kadira team figure out the best way to add automatic snapshot testing to storybook, shouldn't we?

arunoda commented 8 years ago

@luisherranz That's the first job after we properly release storybook 2 in next monday. (with a new website, great docs + new addon API)

mthuret commented 8 years ago

@luisherranz Well, I guess that if somebody wants to absolutely use this jest feature from command line, there's a way to do so :)

Otherwise, yeah, let's wait to see how it will be integrated within storybook.

arunoda commented 8 years ago

I agree with @mthuret . There is no reason not to play with JEST in the meantime.

faceyspacey commented 7 years ago

@mthuret how can you use the snapshot function from facade.js if it will only work within the jest test runner? Should the following file have a snapshot function that does nothing for the case when your test is executed in storybook?

https://github.com/mthuret/storybook-addon-specifications/blob/master/.storybook/facade.js

Furthermore, what's the progress on an independent snapshotting tool that works within storybook (that isn't automatic, i.e. not storyshots)? Do any exist yet? It would be nice to be able to do expect(component).toMatchSnapshot() and have its results recorded in the specifications tab, even if it's just pass/fail (i.e. without a printout/diff of the snap). Maybe I'm missing something, but it seems the only option now is to provide an empty stub for storybook, in which case its simply not run.

faceyspacey commented 7 years ago

@mthuret oh i c in the readme you say "add this line export const snapshot = () => {} just like I was thinking.

I guess that leaves the one remaining goal: to be able to see the result of your snapshot tests in storybook, and be able to take more than one snapshot like storyshots does. If you're testing a components using redux state, you often only want one story, but your test will trigger it in many states, which therefore require multiple manual snapshots.

mthuret commented 7 years ago

Hi @faceyspacey,

I don't know if the storybook team plans to add snapshots results inside the storybook through there storyshots addon.

Regarding the specs addons, we discussed a v2 here: https://github.com/mthuret/storybook-addon-specifications/issues/17#issuecomment-276055887

I think being able to write normal jest tests and then have the results inside the storybook will make possible the kind of thing that you're describing. What do you think?

Anyway, I just need some time to work on this :) Probably not before april though.

faceyspacey commented 7 years ago

hey @mthuret I've spent the last 2 days doing nothing but working with your tool, and I have to admit, it is fantastic!

I've re-made the mocks in a reverse way. So basically I made Storybook and your addon be able to work on standard describe + it tests, with the only variation being, that if you return a story react component from it, stories.add() will be called on it.

Another way to put it is: what you originally had makes it so your tests work in Storybook style code. I made it so jest style tests generate a storybook behind the scenes!

If you're interested in taking a quick peak, check this out:

git clone git@github.com:faceyspacey/animated-transition-group.git
cd animated-transition-group
yarn
npm run storybook
npm run test

https://github.com/faceyspacey/animated-transition-group/blob/master/stories/index.js

https://github.com/faceyspacey/animated-transition-group/blob/master/storybook/facade.js https://github.com/faceyspacey/animated-transition-group/blob/master/storybook/__mocks__/facade.js

It's still a work in progress and I only quickly made expect.toEqual work, but I assume i can generalize it really quickly, perhaps with proxies.

One unique thing I did is made it so that each test is geared toward rendering one state for one component in Storybook. The idea being that Storybook will allow you to see how a component looks in each test! So that means at the beginning of a describe block you setup your component. Then in each it block you perform a redux dispatch, take a snapshot, and return a story from the it block so that Storybook can show you how it looks there. Storybook executes synchronously rather than concurrently like Wallaby (which I use), so that guarantees that if you return the same story component multiple times, when you navigate to the corresponding page in Storybook, you will get that component in the state you expect.

What I also did was made it so expect when called in Storybook land is actually wrapped in an it so that you can see multiple "tests" on each storybook. The only difference is its calls to expect, which is far more useful than just seeing one green/red node for a single test in storybook. Again, the idea is that I made it so there is a one to one pairing between it tests in jest and storybook pages. Just hearing about it may be counter-intuitive, but if you boot it up, you will see it's extremely intuitive.

It may be a bad idea to allow fro setting up the story in the describe block, but if that's the case, beforeEach or a function can be used to re-set it up each time in each it block. One benefit of the it block is that since its executed in the context of stories.add -> specs -> describe knobs will work. Knobs and I assume any similar addons needs to be executed within stories.add. So in short, if that's the case, basically, the interface is as simple as returning a story component from an it block. Checkout the code and let me know what you think. If you have wallaby--or if you dont its free for 30 days--u will see that the snapshot stuff within Storybook is really low priority, as wallaby handles it extremely well (what might be nice is 2-way linking between both Wallaby and Storybook though).

The most important part is that all the components you have to setup can be re-used between Storybook and your actual tests. Just being able to write your tests, and then go take a peak at what they actually look like in application form is extremely nice. It's the perfect compliment to being able to open up wallaby and browse the JSON form, i.e. your snapshots.

faceyspacey commented 7 years ago

ps, i read that other issue #17 the other day too, and perhaps this new interface solves that. Again, it can be summed up by: standard jest describe/it tests, but with one addition: you return your components from it if you want them to appear in Storybook.

mthuret commented 7 years ago

That seems super nice @faceyspacey! I'll definitely have a look at your repo and see what you've built with the specs addons 👍 Maybe it'll also worth adding something in the docs about your strategy :)