jestjs / jest

Delightful JavaScript Testing.
https://jestjs.io
MIT License
44.21k stars 6.46k forks source link

How to mock specific module function? #936

Closed seibelj closed 8 years ago

seibelj commented 8 years ago

I'm struggling with something that I think should be both easy and obvious, but for whatever reason I can't figure it out.

I have a module. It exports multiple functions. Here is myModule.js:

export function foo() {...}
export function bar() {...}
export function baz() {...}

I unmock the module for testing.

jest.unmock('./myModule.js');

However, I need to mock foo, because it makes ajax calls to my backend. I want every function in this file to remain unmocked, expect for foo, which I want to be mocked. And the functions bar and baz make calls internally to foo, so when my test calls bar(), the unmocked bar will call the mocked foo.

It appears in the jest documentation that calls to unmock and mock operate on the entire module. How can I mock a specific function? Arbitrarily breaking up my code into separate modules so they can be tested properly is ridiculous.

develerltd commented 6 years ago

Going back to the original request...

Couldn't you just wrap a proxy around an import *? eg

import * as test from './myfile.js';

const handler = { /* Intercepts: getting properties / get(target, propKey, receiver) { console.log(GET ${propKey}); return 123; },

/** Intercepts: checking whether properties exist */
has(target, propKey) {
    console.log(`HAS ${propKey}`);
    return true;
}};

const p = new Proxy(test);

On Tue, Jan 30, 2018 at 4:24 PM, Denis Loginov notifications@github.com wrote:

@WangHansen https://github.com/wanghansen could you add .mockImplementation() to your jest.spyOn()? As someone coming from Jasmine, I found this tip crucial to achieve the same functionality as Jasmine's spies. E.g.

const mockModuleFunction = jest .spyOn(module, 'function') .mockImplementation(() => 'hello');...expect(mockModuleFunction.mock).toBeCalled();

If you don't use mockImplementation(), then jest.spyOn() produces an object that is not a mock (afaiu) and it actually defers to the native implementation. If you do have to keep native implementation, maybe it's worth using

const moduleFunction = module.function;jest.spyOn(module, 'function').mockImplementation(moduleFunction);...

Not sure this is necessary, but fairly sure it should work.

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/jest/issues/936#issuecomment-361648414, or mute the thread https://github.com/notifications/unsubscribe-auth/AQRY9VXyHNYatwOOY6EV637WGQH9k5Plks5tP0I9gaJpZM4IPGAH .

--

Darren Cresswell Contract Developer | Develer Limited E-mail: darren@develer.co.uk Phone: Website: http://www.develer.co.uk

Please consider the environment before printing this email WARNING: Computer viruses can be transmitted via email. The recipient should check this email and any attachments for the presence of viruses. Develer Limited accepts no liability for any damage caused by any virus transmitted by this email. E-mail transmission cannot be guaranteed to be secure or error-free as information could be intercepted, corrupted, lost, destroyed, arrive late or incomplete, or contain viruses. The sender therefore does not accept liability for any errors or omissions in the contents of this message, which arise as a result of e-mail transmission.

WARNING: Although Develer Limited has taken reasonable precautions to ensure no viruses are present in this email, the company cannot accept responsibility for any loss or damage arising from the use of this email or attachments.

Develer Limited is a limited company registered in England and Wales. | Company Registration No. 09817616 | Registered Offices: SUITE 1 SECOND FLOOR EVERDENE HOUSE, DEANSLEIGH ROAD, BOURNEMOUTH, UNITED KINGDOM, BH7 7DU

WangHansen commented 6 years ago

@dinvlad @iampeterbanjo @SimenB Thanks again for all your help, but unfortunnately, none of the ways you have suggested worked. I wonder if it is because that the function is called in the form of next(err). The logic is, when a request failed, it will be passed into the errorHandler by calling return next(err). For sure the function is being called because when I added console.log, it is printing. But the tests suggest it is never called

WangHansen commented 6 years ago

@dinvlad I tried your method, it didn't work, but thanks for your help anyway. I was just wondering why do you need to call mockImplementation, according to the official doc on jest.spyOn, you only call it when you want to override the original function.

dinvlad commented 6 years ago

@WangHansen yes you're right that it's only needed when one wants to override the original method. I was just throwing an idea for those situations.

One reason why it might have failed for you is asynchronicity. If your method is using callbacks and/or Promises (or async/await), then you need to make sure your expectations are actually executed before your test method terminates. There's a special method expect.assertions(N) to assert that. Also make sure that your expectation is executed only after the code inside callbacks/promises has been called. I'm sure you've looked at that but just for the reference, https://facebook.github.io/jest/docs/en/asynchronous.html

rolandjitsu commented 6 years ago

It's unfortunate that mocking internally used fns as @seibelj describes is not possible without changing the module impl.

In my opinion, tests should not drive how business logic is implemented πŸ˜• (at least not to this degree)

Are there any plans to implement such behaviour in jest?

avshyz commented 6 years ago

Hi, Kinda late to the whole discussion, but reading through the whole discussion - I still haven't managed to do that. @greypants 's promising solution didn't really work for me, as it still calls the original function. Have anything changed since the beginning of this discussion? Am I missing something?

sarahdayan commented 6 years ago

I adapted @greypants's solution a little bit and it worked for me. Here's my code if it can help someone:

import Module from './module'
import { getJSON } from './helpers'

jest.mock('./helpers', () =>
  Object.assign(require.requireActual('./helpers'), {
    getJSON: jest.fn()
  })
)

test('should use the mock', async () => {
  getJSON.mockResolvedValue('foo')
  await expect(module().someAsyncThingThatUsesGetJSON()).resolves.toEqual('foo')
})

test('should use the actual module', () => {
  expect(module().someFunctionThatUsesAHelper()).toBe(true)
})

This still feels kinda hacky and the docs weren't that helpful. I'm grateful for Jest and the team behind it but it seems like a common use case that should at least have an "official" solution.

avshyz commented 6 years ago

@sarahdayan, and whomever it may be of interest - I've ended up using babel-plugin-rewire. It took me a while to find that plugin, but 'twas a solution coherent enough to not feel hacky.

darkowic commented 6 years ago

In most cases, we want to not mock one or more functions from a module. If you use global jest's mocking system you can achieve it using genMockFromModule and requireActual. Here is the example:

//  __mocks__/myModule.js
const moduleMock = jest.genMockFromModule('./../myModule');

// in most cases we don't want to mock `someFunction`
moduleMock.someFunction = jest.fn(
  (...args) => require.requireActual('./../myModule').someFunction(...args)
)

module.exports = moduleMock;

This solution allows using mocks for other functions from the module, using someFunction original implementation when the whole module is mocked and also allows to mock the someFunction function with mockImplementationOnce or mockImplementation API.

clownvary commented 6 years ago

I have read all conversations above, but no one solution works for me. If you are still looking for solution for this test case, answer is babel-plugin-rewire, this plugin is to resolve that scenario case we discussed. Please take a look on that lib, you will thanks me back later.

qwertie commented 6 years ago

So to summarize the whole thread above:

  1. Suppose you have a module m with functions f, g and h where g and h call f. We would like to mock f so that g and h call the mock instead of the real f. Unfortunately this is not possible to do directly unless f is always called through exports as cpojer described. This is impossible if your module uses ES6 import/export syntax in TypeScript (and I'm guessing the same is true in Babel).
  2. However, suppose we move f to another module m2. Then m will have a statement like import {f} from 'm2' and when g and h call f, they actually are calling m2.f where m2 = require('./m2') (that's how the Babel/TypeScript translation will look). This makes it possible to mock f reliably as described by greypants. In other words, you can mock calls reliably only if they cross a module boundary. Note: greypants' solution now produces this error message: "The module factory of jest.mock() is not allowed to reference any out-of-scope variables - Invalid variable access: __assign". I suspect this is a bug in Jest; as a workaround, use Object.assign as shown below.
  3. But if, instead of mocking one or two functions, you want to mock everything except one or two functions, use code like darkowic's.

Example of (2):

// module m.js
import {f} from './m2'
export function g() { return 'f returned ' + f(); };

// module m2.js
export function f() { return 'the real f'; }

// test.js
import * as m from './m'

jest.mock('./m2', () => Object.assign(
  require.requireActual('./m2'), {
      f: jest.fn().mockReturnValue('MOCK')
  }));

test('mocked', () => {
  expect(m.g()).toEqual('f returned MOCK');
});

While testing this I ran into #2649: calling jest.mock inside a test has no effect, and if you call it at global scope then you cannot unmock before other tests. Very annoying.

DalderupMaurice commented 6 years ago

Thanks!! @sarahdayan Been looking for this for a while

SimenB commented 6 years ago

If the docs are lacking, PRs are always welcome to clarify them πŸ™‚

tho-graf commented 6 years ago

Hi everyone!

I played a little bit around and had the following idea to solve that problem:

// m1.js
export const f = () => "original f"

// __mocks__/m1.js
const originalM1 = require.requireActual("../m1");
// set the original as a prototype
let mockModule: any = Object.create(originalM1);
const __setMock = (name, value) => {
  mockModule[name] = value;
};
const __removeMock = (name) => {
  Reflect.deleteProperty(mockModule, name);
};
// enhance the mocked module to allow overriding original exports
module.exports = Object.assign(mockModule, { __setMock, __removeMock });

// m1.test.js
import { f } from "./m1";

jest.mock("./m1");

test("mocking stuff", () => {
  // here nothing is mocked - the original module is used
  expect(f()).toBe("original f");

  // override the export f of the module
  require("./m1").__setMock("f", () => "mocked f");
  expect(f()).toBe("mocked f");

  // set it back to the original
  require("./m1").__removeMock("f");
  expect(f()).toBe("original f");

  //override with another value
  require("./m1").__setMock("f", () => "another mocked f");
  expect(f()).toBe("another mocked f");
});
bannier commented 6 years ago

My 2 cents :

I tested a lot of solutions (if not all of them) and the only one that worked for me is this one (jest 23) :

// importedModule.js
export const funcB = () => {
  return "not mocked";
};
export const funcA = () => {
  return mockProxy.funcB(); // this works but it's soooo hacky
};

export const mockProxy = {
  funcB
};

// moduleToTest.js
import { funcA } from "./moduleImported.js";

export default function() {
  return funcA();
}

// test
let moduleImported = require("./moduleImported.js");
moduleImported.mockProxy.funcB = jest.fn(() => "mocked");
const funcToTest = require("./moduleToTest.js").default; // or import

it("should return mocked", function() {
  expect(funcToTest()).toBe("mocked");
});

I came to the conclusion that it's not a good idea to try to do this because :

jamesone commented 6 years ago

Has anyone found a solution for this that works?

I'm using:

"jest": "^21.2.1",
"jest-cli": "^21.2.1",
bannier commented 6 years ago

@jamesone have you read this https://github.com/facebook/jest/issues/936#issuecomment-410080252 ?

martdavidson commented 5 years ago

This worked for me, based on the answer by @thomaskempel:

In my case, I wanted to mock a dependency in node_modules, lets call it 'shared-components'. It exports a number of named components. I wanted to mock only a couple of these named exports, and leave the rest as the real thing.

So in __mocks__/shared-components.js I have:

const original = require.requireActual('shared-components');

module.exports = {
...original,
moduleNameToOverride: jest.fn().mockImplementation(() => {
      return 'whatever';
    }),
}

In my case I was stubbing out the implementations. Hopefully this helps someone in the future.

daviderama commented 5 years ago

I've faced the same issue recently, the conversation in this thread helped me to understand better and I summarized my findings here https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642

MajorBreakfast commented 5 years ago

Inspired by @qwertie's solution

mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))
rodoabad commented 5 years ago

@MajorBreakfast that does work with React.lazy?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

I still get ReferenceError: React is not defined.

vchinthakunta commented 5 years ago

To mock an independent export function module: Export all of the individual functions part of the default export from the file.

Example: dataDao.js

function getData() function setData() function deleteData() export {getData, setData, deleteData}

Now you can import all of the functions from the file into your jest test by default naming;

dataDao.spec.js

import * as dataDao from '../dataDao'; // Spy on the modules referencing the assigned default name at the import jest.spyOn(dataDao, 'getData') jest.spyOn(dataDao, 'setData') jest.spyOn(dataDao, 'deleteData')

satsukikorin commented 5 years ago

@vchinthakunta, that may work, but it looks like a violation of a major purpose of the export/import syntax: other modules will no longer be able to to import specific methods or data fields via

import { justThisThing } from 'someModule';

Am I missing something there?

craigcartmell commented 5 years ago

@MajorBreakfast that does work with React.lazy?

const mockLazy = jest.fn();

jest.mock('React', () => ({
    ...jest.requireActual('React'),
    lazy: mockLazy
}));

I still get ReferenceError: React is not defined.

I think 'React' should be lowercase here as it's referencing the import?

jest.mock('react'...)
LoganElliott commented 5 years ago

I got my code working using the following which is simpler than other solutions i've seen here. It doesn't require you to use require or set up default exports.

helpers/navigation.js

export const initHeader = () => {
    // initialise header
    ...
}

...

export const anotherHelperFunction = () => {
    // do stuff
    ...
}

component that uses navigation.js

import { initHeader } from '../helpers/navigation';

jest.mock('../helpers/navigation');

...

describe('Test component', () => {

    it('should reinitialise header', () => {
        const mockHeaderInit = jest.fn();
        initHeader.mockImplementation(mockHeaderInit);

        const component = mountComponent(mockProps);
        component.simulate('click');

        expect(mockHeaderInit).toBeCalled();
    }
}
oakis commented 5 years ago
mockGet = jest.fn()
jest.mock('my-module', () => ({
  ...jest.requireActual('my-module'),
  get: mockGet
}))
ReferenceError: mockGet is not defined

       4 | const mockGet = jest.fn();
       5 | jest.mock('./browserStorage', () => ({
       6 |   ...jest.requireActual('./browserStorage'),
    >  7 |   get: mockGet,
         |        ^
       8 | }));
SimenB commented 5 years ago

jest.mock is hoisted, use doMock or

jest.mock('./browserStorage', () => ({
  ...jest.requireActual('./browserStorage'),
  get: jest.fn(),
}));

const {get: mockGet} = require('./browserStorage');
ezmiller commented 5 years ago

This seems like a common enough problem. Is there something about this in the jest docs?

kirillgroshkov commented 5 years ago

So, is there a solution for mocking a function within same module?

zjaml commented 5 years ago

The following method worked for me, the trick is to reset the mocked function back at the end of the test. This example mocks verify function from jsonwebtoken module.

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })
RichieRunner commented 5 years ago

The following method worked for me, the trick is to reset the mocked function back at the end of the test. This example mocks verify function from jsonwebtoken module.

  test('perform my test', async () => {
    // save the real jwt.verify function
    const verify = jwt.verify
    // mock it.
    jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
    // do the test
    ...
    // set the real function back.
    jwt.verify = verify
  })

where are you using the const verify? anyways, this will only work if your mocked function isn't an exported const function

RichieRunner commented 5 years ago

So, is there a solution for mocking a function within same module?

After a lot of searching, the solution for this issue is to store your exports in a single object that your function and mocks can reference to. The below articles all came to the same consensus.

https://github.com/facebook/jest/issues/936#issuecomment-438975674 https://medium.com/@qjli/how-to-mock-specific-module-function-in-jest-715e39a391f4 https://luetkemj.github.io/170421/mocking-modules-in-jest

erikeckhardt commented 5 years ago

It's surprising to me that no one has mentioned a different solution that, if it is workable for your code, completely eliminates all these problems and makes testing the desired code very, very simple:

Move the single function you want to mock into its own module.

Seriously. If your module is written such that you need to test inner parts of it separately from other inner parts, then almost certainly your class is violating the Single Responsibility Principle (yes, it's not a real class, but the module is functioning like a class, modules being a unit container of code). Split that sucker, and boom, you can mock the requires.

If the mocked function relies on a bunch of private state, this still isn't a good reason not to split your module somehow. The very fact that it relies on a bunch of internal state implies, to me, that the concerns of the module are not clearly thought out. Perhaps there is even a third module to split out, that represents some kind of data class or DTO, which can be passed in as an argument.

Also, are you exporting functions just for testing that would otherwise be private? Why is it that external code would call the mocked function directly, but also call the other functions that themselves need to call it? I bet there is some kind of disconnect going on here. Maybe the mocked function needs to stay but all the functions need to be split in half with the half removed going into another module. You get the idea.

When testing gets very hard, it's almost always a sign of refactoring being needed...

No testing tomfoolery required:

const tomfoolery = require('tomfoolery'); // no longer required

dgrcode commented 5 years ago

After reading this thread and testing the proposed solutions, I still can't get this to work. From what I've read some people made this work but I can't figure out how.

Could someone tell me the code I need to add to the following example to make the tests pass?

// a.js
export const foo = () => 'foo-' + bar()
export const bar = () => 'bar'
// a.test.js
import {
  foo,
  bar
} from './a'

describe('foo', () => {
  it('should return foo-MOCKED_BAR', () => {
    expect(foo()).toBe('foo-MOCKED_BAR')
  })

  it('should have the mocked implementation of bar', () => {
    expect(bar()).toBe('MOCKED_BAR')
  })
})

describe('bar', () => {
  it('should have the original implementation of bar', () => {
    expect(bar()).toBe('bar')
  })
})

describe('foo and bar together', () => {
  it('should have the original implementation of both', () => {
    expect(foo()).toBe('foo-bar')
  })
})

Thanks!

vvo commented 5 years ago

I wanted to only mock a single lodash method like lodash.random and was able to do it easily with:

module.js

const lodash = require('lodash');

module.exports = function() {
  return lodash.random();
}

test.js

const lodash = require('lodash');
const module = require('./module.js);

it('mocks lodash', () => {
    jest.spyOn(lodash, 'random').mockImplementationOnce(() => {
      return 2;
    });

    expect(module()).toEqual(2)
});

Hope that helps :)

arbielsk commented 5 years ago

Something that worked for our team working with typescript was to create a const we export instead of exporting the function directly. Not working: export function doSomething(a, b) {} Working: export const doSomething = function (a, b) {}

joway commented 5 years ago

I changed export like what @arbielsk do, it works! But I dont know what's the difference between two kinds of export...

yoni-abtech commented 5 years ago

@dgrcode Did you ever find a solution to your example? As far as I can tell, what you're trying to do is not supported by mocking via Jest. Specifically, I think that mocking in just is basically rewiring imports so that external views of the module see the mocked methods. However, in your example, foo and bar are in the same module and so foo's view of bar can't be mocked out.

I believe that your options are either: 1) Re-organize your code so that foo imports a module that includes bar 2) Use babel-plugin-rewire

Please correct me if I'm misunderstanding!

ssoloff commented 5 years ago

I had a slightly different requirement: I wanted to mock an entire module except for one function. Using @MajorBreakfast's solution as a starting point, I came up with the following:

jest.mock('my-module', () => ({
  ...jest.genMockFromModule('my-module'),
  myFunction: jest.requireActual('my-module').myFunction
}))
etiennejcharles commented 5 years ago

@dgrcode Did you ever find a solution to your example? As far as I can tell, what you're trying to do is not supported by mocking via Jest. Specifically, I think that mocking in just is basically rewiring imports so that external views of the module see the mocked methods. However, in your example, foo and bar are in the same module and so foo's view of bar can't be mocked out.

I believe that your options are either:

  1. Re-organize your code so that foo imports a module that includes bar
  2. Use babel-plugin-rewire

Please correct me if I'm misunderstanding!

That was actually my case My understanding of how modules could be mocked was all messed up πŸ€¦β€β™‚

Basically If 2 function are in the same module, and calling one another

If foo is calling bar

function bar() {
  return 'some-result'
}

function foo(){
  return bar()  // <-- the function I want to mock 
}

Put bar in a new file (to be mocked)

I moved the bar method in a new file, and now I could use many of the examples above Big thanks to @yoni-abtech in making me understand that 🀣

nickofthyme commented 5 years ago

The way I see it after reading this whole thread and testing over and over, there are 3 options....

Option 1 - declare all functions using const

This requires you to mandate the use of function expressions over declarations. Fortunately, the func-style eslint rule has got your back.

Using export const allows you to spyOn functions that are used by other functions from within the same module.

// hello.js
export const message = () => {
  return 'Hello world';
}

export const foo = () => {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test spyon with function expressions', function () {
  afterAll(() => {
    jest.restoreAllMocks();
  });
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    jest.spyOn(testModule, 'message').mockReturnValue('my message');

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
  });
});

Option 2 - Use rewire babel plugin

If you don't want to mandate the use of function expressions (i.e. using const) this could be a good approach.

This allows you to rewire (aka mock) functions from the same module. I could imagine the code would look something like that below but haven't tested it. Also from their docs it looks like you can rewire functions in the same module that are not even exported from the module πŸ‘, imagine the example below without exporting the message function.

Example:

// hello.js
export function message() {
  return 'Hello world';
}

export function foo() {
  return message();
}
// hello.test.js
import * as testModule from './hello.js';

describe('test rewire api', function() {
  it('should NOT mock message in foo', function () {
    const actual = testModule.foo();

    expect(actual).toBe('Hello world');
  });

  it('should mock message in foo', function () {
    testModule.__RewireAPI__.__Rewire__('message', jest.fn().mockReturnValue('my message'));

    const actual = testModule.foo();

    expect(actual).toBe('my message');
    expect(testModule.message).toHaveBeenCalledTimes(1);
    testModule.__RewireAPI__.__ResetDependency__('message');
  });
});

See the docs for examples.

Note: Requires babel transpilation

Option 3 - Separate all functions into seperate modules/files

This option is the least favorable but would clearly work just fine with the typical mock functionality.


PS: Although this tread was very enlightening and often entertaining, I hope this synopsis mitigates the need for others to read the whole thread. ✌️

ckbrewer33 commented 5 years ago

Thanks @nickofthyme, you just ended a couple days of banging my head against this.

danielhusar commented 4 years ago

@nickofthyme your option 1 fails in both my app, and create react app:

 FAIL  src/hello.test.js
  test spyon with function expressions
    βœ“ should NOT mock message in foo (3ms)
    βœ• should mock message in foo (6ms)

  ● test spyon with function expressions β€Ί should mock message in foo

    expect(received).toBe(expected) // Object.is equality

    Expected: "my message"
    Received: "Hello world"

      17 |     const actual = testModule.foo();
      18 |
    > 19 |     expect(actual).toBe("my message");
         |                    ^
      20 |     expect(testModule.message).toHaveBeenCalledTimes(1);
      21 |   });
      22 | });

      at Object.toBe (src/hello.test.js:19:20)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        1.848s
Ran all test suites related to changed files.
nickofthyme commented 4 years ago

@danielhusar You appear to be correct. Sorry, I should have tested this with CRA.

I got the mocking described in option 1 to work here. Test it using the yarn test:hello script.

Result

> yarn test:hello
yarn run v1.16.0
$ jest --config=jest.config.js -t=hello --verbose
 PASS  src/hello/hello.test.ts
  test hello
    βœ“ should NOT mock message in foo (3ms)
    βœ“ should mock message in foo (1ms)

Test Suites: 1 skipped, 1 passed, 1 of 2 total
Tests:       1 skipped, 2 passed, 3 total
Snapshots:   0 total
Time:        1.392s
Ran all test suites with tests matching "hello".
✨  Done in 2.36s.

It requires using a custom jest.config.js file using ts-jest and calling jest --config=./jest.config.js directly, not through react-scripts. I am not sure how jest is configured in the react-scripts but I think there may be a way to update the config somehow.

This fix removes the transforms for *.css and *.svg files, so ignore the App.tsx errors.

danielhusar commented 4 years ago

Is there anything particular that needs to be done in order to make it work? I would say I have pretty standard setup (without ts) and it doesn't work out of the box.

nickofthyme commented 4 years ago

I’ll look at it a little more tonight and see if that’s possible.

nickofthyme commented 4 years ago

@danielhusar I looked into last night and couldn't get an out of the box solution. The key is the jest config transformer which CRA allows you to override in package.json#jest. The js and ts files are transpiled using babel-jest but react-scripts blocks you from using a .babelrc config file and setting the test env which they set in react-scripts test here.

I wish I could dig deeper but don't have the time right now.

danielhusar commented 4 years ago

Hmm Im still bit struggling to make it work (on my custom setup, not the cra). (latest version of both jest and babel-jest)

This is my jest config:

module.exports = {
  name: 'my-app',
  testURL: 'http://localhost/',
  setupFiles: ['<rootDir>/setup-jest.js'],
  setupFilesAfterEnv: ['<rootDir>/setup-test.js'],
  testMatch: ['**/__tests__/**/*.test.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
  testEnvironment: 'jest-environment-jsdom-fifteen',
  snapshotSerializers: ['enzyme-to-json/serializer'],
  globals: {
    ENV: 'test',
  },
  transform: {
    '^.+\\.[t|j]sx?$': 'babel-jest',
  },
};

And my babelrc:

{
  "presets": [
    "@babel/react",
    ["@babel/env", {
      "corejs": "3",
      "useBuiltIns": "entry",
      "loose": true,
      "exclude": [
        "es.string.split"
      ]
    }],
    "@babel/flow"
  ],
  "plugins": [
    "array-includes",
    "lodash",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-syntax-class-properties",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-optional-chaining"
  ],
  "env": {
    "test": {
      "plugins": ["dynamic-import-node"]
    }
  }
}
groupsky commented 4 years ago

For anyone struggling with option 1, it's important to use () => { return expression } instead of () => (expression) function.

Aequitas737 commented 4 years ago

I got option 1 to work by modifying it to:

import * as test from './test';

export const message = () => {
    return 'Hello world';
  }

  export const foo = () => {
    return test.message();
  }

Not pretty but it should work.