Simulate C#’s InternalsVisibleTo in TypeScript

There are some cases where you have conflict of interest in designing your libraries. You want to reduce the amount of complexity exposed to the consumers and provide clean abstractions; however, your unit tests need to mock out dependencies or override some values not intended for consumers to modify. When you run into this scenario C#’s internal and InternalsVisibleTo help solve this by allowing you to add those extra extensions needed for special setup while only exposing them to the assemblies you explicitly declare such as tests.

In this article we’ll take a look at how to achieve a similar result in TypeScript by being careful about which modules we expose at the root of the library.

First let’s establish the problem we’re solving by using example in C#:

When unit testing Foo’s behaviors we want to mock out its dependencies for proper test isolation. This would require taking IBar instance as parameter however we don’t want our consumers to have to manually create this instance since it’s leaking out setup details of Foo. In other words, we don’t want to require consumers to write new Foo(10, new Bar()) every time they want an instance Foo.

This is where the internal modifier and InternalsVisibleTo can help. They allow us to add that extra constructor and only expose it to our test project like below:

Now that we understand the value. Imagine you come across a similar scenario when working with TypeScript and you would like to achieve the same result, but you don’t have the same tools of internal InternalsVisibleTo , or even the ability to have multiple constructors. TypeScript has other tools such as modules which allow us to explicitly control what is exposed at the root of the libraries entry point which is different that C#. We leverage this to expose a special class for testing which has the parameter taking an instance of IBar to the tests without exposing it to consumers.

Notice that the index.ts only exposes Foo class from ./foo module, not blindly exposing all of it. Yet in the tests we still can import InternalFoo since we’re compiled with the source and not acting as external consumer of compiled library output.

This image below explains the module dependency diagram to explain visually:

Image for post
Image for post

Here is a quick video explaining the concept:

If you know other techniques like this to simulate features of other languages within TypeScript let me know in the comments.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store