I think I figured out the best way to do it, that is flexible and suits all cases.
First of all, utility functions should be exported independently, no need to wrap them in a class. This allows tree-shaking to work with the file's exports.
utils.ts
export function helper() {
console.log('help is provided')
}
Than you can import and use the functions directly, if you want tree-shakability and don't care about mocking utils functions (which you don't in most cases, if they are written correctly).
But, if for some components you want to mock them, you can still do it.
For Angular, you should do the following:
tokens.ts
import * as utils from 'utils.ts';
export const utilsToken = new InjectionToken<typeof utils>('UtilsToken')
app.module.ts
import * as utils from 'utils.ts';
import { utilsToken } from 'tokens.ts';
@NgModule({
providers: [{ provide: utilsToken, useValue: utils }]
})
Having this set up, you can inject your utils in any component instead of using them directly:
component.ts
import * as utils from 'utils.ts';
import { utilsToken } from 'tokens.ts';
@Component({
})
export class Component {
constructor(@Inject(utilsToken) private _utils: typeof utils) {
this._utils.helper();
}
component.spec.ts
import * as utils from 'utils.ts';
import { utilsToken } from 'tokens.ts';
...
await TestBed.configureTestingModule({
providers: [{ provide: utilsToken, useValue: mockUtils }],
}).compileComponents(),
Then you can use your utils directly in some components and inject them in others (that require mocked utils version for testing)