As far as code style, the barrel results in cleaner imports — less import code at the top of your file and easier to read locations of those imports. Code style is highly opinionate though, and not reason enough to make a decision. The most important reason is the benefits to testing. Mocking imports should be easy, and barrels help with that significantly.
Circular dependencies are usually just a warning, and you can ignore them. You may even be able to turn the warning off. I have a project where we use circular dependencies via barrels (and ignore the warning) and one where we don’t (where circular dependencies are treated as errors). In cases of circular dependencies, it is usually because the import is not shared at that scope. It may belong as a child, e.g.
/type/name1/type/name2/type/HERE instead of
/type/name1/type/HERE. This article talks about having a maximum depth of 2, but this maximum depth absolutely contributes to circular dependencies, where modules import from their own barrel even though that imported dependency only belongs to that module. Consider a deeper maximum depth and moving those dependencies there. The choice to have a maximum depth was developer feedback that too deep of a depth was hard to navigate. This is strictly an opinionated topic with no impact on testing or customers. You are free to change your max depth if circular dependencies matter more to you. You may also find that the barrels are too generic. Your
utils/name is importing from
utils because its dependency is too generically “utils,” and instead could be something like
As an end-all, be-all solution, you can ignore barrels and mock each module individually using:
import * as moduleName from './type/namex';
jest.spyOn(moduleName, 'default'); // <-- mock the default export
This is just more verbose importing in each module and in each test, but it’s entirely up to you and your team what is important in the long-term maintenance of your product.