npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hirez_io/jest-single

v1.1.8

Published

A jest addon that helps you write 'Single-Action Tests' by breaking them into a given / when / then structure.

Downloads

988

Readme

@hirez_io/jest-single 📃👌

A Jest addon that helps you write 'Single-Action Tests' by breaking them into a "given / when / then" structure.

npm version npm downloads codecov Build and optionally publish lerna Code of Conduct License: MIT All Contributors

Table of Contents

Installation

yarn add -D @hirez_io/jest-single

or

npm install -D @hirez_io/jest-single

Configuring Jest

Add the following line to your jest config:

"setupFilesAfterEnv": ["node_modules/@hirez_io/jest-single/dist/jest-single.js"]

ATTENTION: If you have configured rootDir -

Make sure you have the right path to node_modules.

For example:

"rootDir": "src",
"setupFilesAfterEnv": ["../node_modules/@hirez_io/jest-single/dist/jest-single.js"]

Using TypeScript?

You should add @hirez_io/jest-single to your types property under compilerOptions in your tsconfig.json (or tsconfig.spec.json) like this:

// tsconfig.json or tsconfig.spec.json

{
  ...
  "compilerOptions": {
    "types": [
      "jest",
      "@hirez_io/jest-single", // 👈 ADD THIS

      // ...any other types you might have...
    ],
    ...
  }
  ...
}

ATTENTION: If you have typeRoots configured like this -

"compilerOptions": {
  "typeRoots": [
    "node_modules/@types"
  ],
}

You should add "node_modules" like this -

"compilerOptions": {
  "typeRoots": [
    "node_modules/@types",
    "node_modules/@hirez_io" // 👈 ADD THIS
  ],
}

or else it won't find @hirez_io/jest-single global types.

VS CODE USERS:

Add the above configuration (types and/or typeRoots) to your tsconfig.json specifically or else it would not recognize the global types.

What are "single-action" tests?

A single-action test is a test with only one action. (CAPTAIN OBVIOUS! 🦸‍♂️😅)

Normally, you setup the environment, you call an action and then you check the output.

What's an action?

Well... it can be a method call, a button click or anything else our test is checking.

The big idea here is that it should be only ONE ACTION PER TEST.

Why writing single-action tests is good?

Single action tests are more "Test Effective" compared to multi-action tests.

The benefits of single-action tests:

✅ Your tests will break less often (making them more effective)

✅ Whenever something breaks, you have only one "action" code to debug

✅ They promote better coverage (easier to see which cases are still missing)

✅ They give you better structure (every part of your test has a clear goal)

How to write single-action tests?

This library follows the natural "given, when and then" structure (some of you know it as "Arrange, Act, Assert").

This means every test has only 3 parts to it, no more.

describe('addTwo', () => {
  
  // This is where you setup your environment / inputs
  given('first number is 1', () => {
    const firstNum = 1;

    // This is where you call the action under test
    when('adding 2 to the first number', () => {
      const actualResult = addTwo(firstNum);

      // This is where you check the outcome
      then('result should be 3', () => {
        expect(actualResult).toEqual(3);
      });
    });
  });

});

It also prints a nicer test description when there's an error:

CONSOLE OUTPUT:
~~~~~~~~~~~~~~

GIVEN first number is 1
WHEN adding 2 to the first number
THEN result should be 3

What's wrong with using it() or test() for single-action tests?

Did you know that the most common way of writing JavaScript tests dates back to 2005? 😅

Jasmine, which was created in 2009 was inspired by Ruby's testing framework - RSpec which was created in 2005.

Jest, which is based on Jasmine added an alias called test() which is basically the same thing.

Originally, RSpec introduced the syntax of "describe > context > it", where context was meant to be used as the "setup" part of the test.

Unfortunately, the context wasn't ported to Jasmine so we got used to writing our tests in the "describe > it" structure... which is more limited.

Here are a couple of limitations with the common it() structure:

❌ 1. It promotes partial or awkward descriptions of tests

The word "it" kinda forces you to begin the description with "should" which leads to focusing specifically on just the "outcome" part of the test (the then).

But if you want to add more context (like what should be the input that causes that outcome) things start to get messy.

Because there isn't a clear convention, people tend to invent their own on the fly which creates inconsistency.

Example:

it('should do X only when environment is Y and also called by Z But only if...you get the point', ()=> ...)

❌ 2. Nothing prevents you from writing multi-action tests

This mixes up testing structures and making them harder to understand

Example:

it('should transform products', ()=> {

  // SETUP
  const fakeProducts = [...];

  // ACTION
  const result = classUnderTest.transformProducts(fakeProducts);

  // OUTCOME
  const transformedProducts = [...];
  expect(result).toEqual(transformedProducts);

  // ACTION
  const result2 = classUnderTest.transformProducts();

  // OUTCOME
  expect(result2).toEqual( [] );


  // this 👆 is a multi-action test.

})

❌ 3. Detailed descriptions can get out of date more easily

The farther the description is from the actual implementation the less likely you'll remember to update it when the test code changes

Example:

test('GIVEN valid products and metadata returned successfully WHEN destroying the products THEN they should get decorated', ()=> {

  const fakeProducts = [...];
  const fakeMetadata = [...];
  mySpy.getMetadata.mockReturnValue(fakeMetadata);

  const result = classUnderTest.transformProducts(fakeProducts);

  const decoratedProducts = [...];
  expect(result).toEqual(decoratedProducts);

})

Did you spot the typo? 👆😅

(it should be "transforming" instead of "destroying")

Compare that to -


  given('valid products and metadata returned successfully', () => {
    const fakeProducts = [...];
    const fakeMetadata = [...];
    mySpy.getMetadata.mockReturnValue(fakeMetadata);
    
    //        👇 --> easier to spot as it's closer to the implementation
    when('destroying the products', () => { 
      const result = classUnderTest.transformProducts(fakeProducts);

      then('they should get decorated', () => {
        const decoratedProducts = [...];
        expect(result).toEqual(decoratedProducts);
      });
    });
  });

Usage

▶ The basic testing structure

The basic structure is a nesting of these 3 functions:

given(description, () => {
  when(description, () => {
    then(description, () => {
 
    })
  })
})
  
    

EXAMPLE:

describe('addTwo', () => {
  
  // This is where you setup your environment / inputs
  given('first number is 1', () => {
    const firstNum = 1;

    // This is where you call the action under test
    when('adding 2 to the first number', () => {
      const actualResult = addTwo(firstNum);

      // This is where you check the outcome
      then('result should be 3', () => {
        expect(actualResult).toEqual(3);
      });
    });
  });

});

Under the hood it creates a regular it() test with a combination of all the descriptions:

CONSOLE OUTPUT:
~~~~~~~~~~~~~~

GIVEN first number is 1
WHEN adding 2 to the first number
THEN result should be 3

▶ Meaningful error messages

This library will throw an error if you deviate from the given > when > then structure.

So you won't be tempted to accidentally turn your single-action test into a multi-action one.

describe('addTwo', () => {
  
    // 👉 ATTENTION: You cannot start with a "when()" or a "then()"
    //                the test MUST start with a "given()"


  given('first number is 1', () => {
    const firstNum = 1;

    // 👉 ATTENTION: You cannot add here a "then()" function directly 
    //                or another "given()" function

    when('adding 2 to the first number', () => {
      const actualResult = addTwo(firstNum);

      // 👉 ATTENTION: You cannot add here a "given()" function
      //                or another "when()" function

      then('result should be 3', () => {
        expect(actualResult).toEqual(3);


        // 👉 ATTENTION: You cannot add here a "given()" function
        //                or a "when()" function or another "then()"
      });
    });
  });

});

async / await support

Example:

describe('addTwo', () => {

  given('first number is 1', () => {
    const firstNum = 1;
    //                                    👇
    when('adding 2 to the first number', async () => {
      const actualResult = await addTwo(firstNum);

      then('result should be 3', () => {
        expect(actualResult).toEqual(3);
      });
    });
  });

});

done() function support

The given function supports the (old) async callback way of using a done() function to signal when the test is completed.

describe('addTwo', () => {
  //                           👇
  given('first number is 1', (done) => {
    const firstNum = 1;

    when('adding 2 to the first number', () => {
      const actualResult = addTwo(firstNum, function callback() {

      then('result should be 3', () => {
          expect(actualResult).toEqual(3);
          done();
        });

      });
    });

  });

});

ℹ It also supports done(error) or done.fail(error) for throwing async errors.

Contributing

Want to contribute? Yayy! 🎉

Please read and follow our Contributing Guidelines to learn what are the right steps to take before contributing your time, effort and code.

Thanks 🙏

Code Of Conduct

Be kind to each other and please read our code of conduct.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!

License

MIT