On the csproj
, we add the InternalsVisibleTo
to enable my test project to access anything internal on this project.
What does this mean? It means that anything internal, from methods to classes, can be accessed by that specific project.
Now, you can write tests like this:
[Fact]
public void GivenValidCustomer_WhenCheckIfIsValid_ReturnTrue()
{
var service = new AddCustomerService();
var isValid = service.IsValid(new Customer("Guilherme", "me@gsferreira.com", null));
isValid.Should().BeTrue();
}
[Fact]
public void GivenInvalidCustomer_WhenCheckIfIsValid_ReturnFalse()
{
var service = new AddCustomerService();
var isValid = service.IsValid(new Customer("", "me@gsferreira.com", null));
isValid.Should().BeFalse();
}
As you can see, I am invoking IsValid
, an internal method. I'm asserting that if I provide a valid customer, it should return "True". So, if I provide an invalid customer, for example, sending an empty name, it should be "False".
If we run the tests, they will succeed.
But now, let's go back and refactor some code.
Let's imagine I want to perform a refactoring on the IsValid
because the method has been growing and becoming too hard to maintain.
Now you feel that this method should go to a different Class. Maybe this Class will be used in another place of your application, for example. So, what will you be doing in that case?
Let's move this into a different Class. I will create a new Class with the name CustomerValidator
. This Class will be Internal as well, and I will copy the method into that. Cut and paste.
class CustomerValidator
{
internal bool IsValid(Customer customer)
{
if (customer.Name is { Length: < 1 })
return false;
if (!customer.Email.IsValidEmail())
return false;
//...
return true;
}
}
Now we can update the AddCustomerService
to:
public class AddCustomerService
{
private readonly CustomerValidator _customerValidator = new();
public void Add(Customer customer)
{
if (!_customerValidator.IsValid(customer))
throw new InvalidDataException("Invalid Customer data");
// ...
}
}
We have to agree that this refactoring has not changed any application behavior. So, in theory, my test should still be Green, am I right? But in fact, they are not. They will not even compile because I changed the structure of my application.
What if the tests were like this?
[Fact]
public void Alternative_GivenValidCustomer_WhenCheckIfIsValid_ReturnTrue()
{
var service = new AddCustomerService();
var action =
() => service.Add(new Customer("Guilherme", "me@gsferreira.com", null));
action.Should().NotThrow<InvalidDataException>();
}
[Fact]
public void Alternative_GivenInvalidCustomer_WhenCheckIfIsValid_ReturnFalse()
{
var service = new AddCustomerService();
var action =
() => service.Add(new Customer("", "me@gsferreira.com", null));
action.Should().Throw<InvalidDataException>();
}
They are performing the same assertion. Better than that, our refactoring didn't break them. These tests, instead of depending on internal methods, are going through the public interface.
And why do I do this? Because any change to internal things should have no reflection on the outside behavior through the public contracts. And while changing the public Contract may be a tremendous problem for you, changes to Internal and Private things should be frictionless.
My point is that when you use the InternalsVisibleTo
for the sake of testing, more often than not, you will be structure dependent. You are testing the code structure instead of the behavior of your application.
There's one scenario that I believe InternalsVisibleTo
may be useful, and let me explain why.
Let's look into this open-source project, octokit.net. It's a GitHub API client. Let's take a look at this example.
var github = new GitHubClient(new ProductHeaderValue("MyAmazingApp"));
var user = await github.User.Get("half-ogre");
Console.WriteLine(user.Followers + " folks love the half ogre!");
Here you can see how you can use it. If you look into this snippet, you can see that what we'll be doing if we want to use this code is that we simply instantiate the GitHub client.
So what does this mean? It means that when you call the User.Get
, there's an HTTP request to the GitHub API.
So what's the relationship between this I'm showing you and the InternalsVisibleTo
?
In a scenario like this, I might want to define a clear and strict contract with the outside world to publish a library. So, I don't want to expose extra things. I want to provide a lean public contract to optimize the consumer developer experience.
So, to test my library, the GitHub Client, I may struggle to avoid invoking the real API or run a fake/mock server to test it. So, in some cases like this, when exposing a library, it may be super useful to have the InternalsVisibleTo
to do a kind of subcutaneous test where you don't use the real API.
For most of the tests, you want to be sure they run fast. So, you can go through this approach.
It's common to see people advocating for the InternalsVisibleTo
, saying that even Microsoft on the .NET repos uses this a lot (which is true). However, it's true because Microsoft is building libraries and frameworks like this GitHub Client API. So, they need to do that, so we don't suffer as consumers. In public libraries, the friction of adoption is an important concern. We need to think about developer experience.
And now, do you agree with me? Will you keep using InternalsVisibleTo
?
Follow me on Twitter (@gsferreira), and let's keep in touch.