In my working routine, I face tons of problems with untestable code. It may happen even in those projects for which 100 percent test coverage is the main acceptance criteria.
I would like to admit that a “Good code” and “Unit-testable code” are not always equivalent terms.
Your code can be understandable, self-documented, but untestable at the same time. There is one universal tip for writing a unit-testable code. You should just use the principles DRY, KISS, and SOLID.
Unfortunately, developers may ignore them to benefit from timesaving or because of the lack of competence. Anyway, situations, when you need to write a test for a fundamentally untestable code, are more frequent than might be wished.
Being a developer with more than 6 years of experience, I would like to share my life hacks and acquired knowledge of testable code. In this article, I will try to list the most common problems and solutions for resolving them. Further, you may find the squeeze of my personal experience and source code examples from C#, MsTest, and Moq.
In my opinion, operator new is the most horrible thing you may meet in the code awaiting unit tests. The value of this operator is doubtful because its usage is a violation of the Dependency inversion principle in SOLID.
Let’s refer a simple example:
<code class="hljs reasonml" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">public <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> CertificateManager
{
public void <span class="hljs-constructor" style="box-sizing: inherit;">CreateCertificate(CertificateDto <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>)</span>
{<span class="hljs-operator" style="box-sizing: inherit;">
...
</span>var certificate = <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">new</span> <span class="hljs-constructor" style="box-sizing: inherit;">Certificate(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Name, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Url, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Content)</span>;
certificate.<span class="hljs-constructor" style="box-sizing: inherit;">Upload()</span>;
}
}</code>
This method is bad for a single reason: it is not testable by any means.
It exists only to call Upload (). However, we can not create a mock-object (mocking) Certificate; therefore, we can not check if it was really “called.”
It is impossible. There are three ways to solve the situation.
1. Straightforward. We can delegate Certificate instantiation to a factory method.
<code class="hljs reasonml" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">public <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> CertificateManager
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> CertificateCreator _certificateCreator;
public <span class="hljs-constructor" style="box-sizing: inherit;">CertificateManager(CertificateCreator <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateCreator</span>)</span>
{
_certificateCreator = certificateCreator;
}
public void <span class="hljs-constructor" style="box-sizing: inherit;">CreateCertificate(CertificateDto <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>)</span>
{<span class="hljs-operator" style="box-sizing: inherit;">
...
</span>var certificate = _certificateCreator.<span class="hljs-constructor" style="box-sizing: inherit;">Create(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Name, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Url, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Content)</span>;
сertificate.<span class="hljs-constructor" style="box-sizing: inherit;">Upload()</span>;
}
}</code>
Now we can create the mock of _certificateCreator and certificate itself.
Pros: It doesn’t change the code structure.
Cons: This approach requires the creation of two new types. We are complicating the code in order to create unit tests. It gets hard to understand the reason for the usage of a fabric method. It may cause a violation of the Single responsibility principle (SOLID).
2. Moving the instantiation to the upper level.
<code class="hljs angelscript" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">CertificateManager</span>
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> CreateCertificate(Certificate certificate)
{
...
certificate.Upload();
}
}</code>
In this example, we send Certificate as an argument and can mock it.
Pros: We simplify the method.
Cons: It requires changing the internal structure of the code. It may cause issues and require to rewrite tests for the calling method. This refactoring requires more time for implementation. Anyway, this approach is the best because it improves the architecture of the application in general.
3. Move of the functionality to the lower level.
<code class="hljs reasonml" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">public <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> CertificateManagerpublic <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> CertificateManager
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> CertificateUploader _certificateUploader;
public <span class="hljs-constructor" style="box-sizing: inherit;">CertificateManager(CertificateUploader <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateUploader</span>)</span>
{
_certificateUploader = certificateUploader;
}
public void <span class="hljs-constructor" style="box-sizing: inherit;">CreateCertificate(CertificateDto <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>)</span>
{<span class="hljs-operator" style="box-sizing: inherit;">
...
</span>var certificate = <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">new</span> <span class="hljs-constructor" style="box-sizing: inherit;">Certificate(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Name, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Url, <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificateDto</span>.Content)</span>;
_certificateUploader.<span class="hljs-constructor" style="box-sizing: inherit;">Upload(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">certificate</span>)</span>;
}
}</code>
In this solution, Certificate becomes a flat model, and we don’t need to mock it. Instead, we mock CertificateUploader.
Pros: It does not change the structure of the code but transforms Certificate to a flat model.
Cons: The test creation process becomes more complicated because we need to check an argument in certificateUploader. Upload for accordance with the template. It will look this way:
<code class="hljs jboss-cli" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">certificateUploader.Verify<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">(<span class="hljs-attr" style="box-sizing: inherit;">x</span> => x. Upload(
It.Is<Certificate>
(<span class="hljs-attr" style="box-sizing: inherit;">arg</span> =>
arg.<span class="hljs-attr" style="box-sizing: inherit;">Name</span> == certificateDto.Name
&& arg.<span class="hljs-attr" style="box-sizing: inherit;">Url</span> == certificateDto.Url
&& arg.<span class="hljs-attr" style="box-sizing: inherit;">Content</span> == certificateDto.Content
)</span>
), Times.Once);</code>
In the example above, the class has only three fields, that is why the comparison of that class with a template is not a complicated task. However, in real projects, there can be more than ten properties in one class. Some developers write some kinds of “half-tests” to simplify the process:
<code class="hljs reasonml" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">certificateUploader.<span class="hljs-constructor" style="box-sizing: inherit;">Verify(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">x</span> => <span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">x</span>. Upload(It.IsAny<Certificate>)</span>, Times.Once);</code>
Such an approach brings a small profit. It is checking the fact of the call but ignores the instantiation of the argument. Besides, the comparison of a template requires that all testing object properties, which relates to a template, are public. Otherwise, we need to refuse the full testing or make the part of the class properties public, which interferes encapsulation. That’s why I don’t recommend using this approach unless its use is of critical necessity.
This issue is almost invisible, but it can cause a bunch of problems.
<code class="hljs lasso" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> ProcessCertificate(Certificate certificate)
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">var</span> ownerName = certificate.Owner.Name;
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}</code>
“Just next to nothing” you may think. Right, it is a tiny nuance. Unless this tiny problem transforms the process of Certificate mocking to the mind-boggling experience. To fix the issue with failing NullReferenceException, we need to mock Owner too. In general, it is a bad practice to set up multilevel access to the class properties or methods. However, If there is a case when you need to test the code with such a structure you can use these solutions:
1. Transferring of responsibility for retrieving properties to an upper level.
<code class="hljs cpp" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ProcessCertificate</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">(Certificate certificate, <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">string</span> ownerName)</span>
</span>{
...
}</code>
Pros: It requires a minimal change in the structure of the code.
Cons: The call will look the following way:
<code class="hljs css" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">_<span class="hljs-selector-tag" style="box-sizing: inherit; color: rgb(180, 142, 173);">certificateManager</span><span class="hljs-selector-class" style="box-sizing: inherit; color: rgb(191, 97, 106);">.ProcessCertificate</span>(<span class="hljs-selector-tag" style="box-sizing: inherit; color: rgb(180, 142, 173);">certificate</span>, <span class="hljs-selector-tag" style="box-sizing: inherit; color: rgb(180, 142, 173);">certificate</span><span class="hljs-selector-class" style="box-sizing: inherit; color: rgb(191, 97, 106);">.Owner</span><span class="hljs-selector-class" style="box-sizing: inherit; color: rgb(191, 97, 106);">.Name</span>);</code>
We may hardly call it a good solution.
2. Extension of the class.
Add to Certificate a property or method, that takes responsibility for multilevel access.
<code class="hljs angelscript" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">Certificate</span>
{
...
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> virtual <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">string</span> OwnerName => <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">this</span>.Owner.Name;
...
}</code>
or
<code class="hljs cpp" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-class" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Certificate</span>
{</span>
...
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">string</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">GetOwnerName</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">return</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">this</span>.Owner.Name;
}
...
}</code>
The method will turn into this:
<code class="hljs lasso" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> ProcessCertificate(Certificate certificate)
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">var</span> ownerName = certificate.OwnerName;
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}</code>
or
<code class="hljs lasso" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> ProcessCertificate(Certificate certificate)
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">var</span> ownerName = certificate.GetOwnerName();
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}</code>
Accordingly.
Pros: There is no need to change the structure of the code.
Cons: This approach has no obvious disadvantages. However, we shouldn’t add to the code of an added property or method, logic, not related to multi-level access. If there is logic, we have to write additional tests to check it. If the class does not have an interface, it is important not to forget to make the new property or method virtual.
The usage of static makes you addicted to the mock-libraries (mockers). Some mockers can create mocks for static classes (AFAIK most of them are paid, but you may try to find a free one). The best solution is to avoid static classes and methods in your code at all. But it is not always possible, because a lot of well-known frameworks still use it nowadays. If you need to test a static one, you can wrap it with the regular class and mock it the usual way.
<code class="hljs cs" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">static</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">StaticLegacy</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">static</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">StaticDo</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
}
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">interface</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ILegacyAdapter</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>;
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">LegacyAdapter</span> : <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ILegacyAdapter</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
StaticLegacy.StaticDo();
}
}</code>
Pros: You can hardly find one, but this approach allows testing of a method with a static call.
Cons: We will have to create two extra types for the unit tests.
The private methods are the core of encapsulation. However, they can become a real headache during unit tests creation. I don’t appeal to refuse the usage of private methods at all, but you should think twice before creating them.
Everything is fine if the private method is just a part of the public one. We can test it in the context of the public method. The problems appear when several public methods share one private.
<code class="hljs lasso" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> class SomeClass: ISomeClass
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> IHelper _helper;
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> SomeClass(IHelper helper)
{
_helper = helper;
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> Do1()
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
_helper.<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">Do</span>();
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> Do2()
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
_helper.<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">Do</span>();
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}
}</code>
We can test PrivateDo in the context of both public methods, but it will lead to the duplication of tests and, consequently, to a doubling of the work when changing PrivateDo (as an alternative — to the complication of the unit test structure, which is undesirable). You can test PrivateDo in the context of only one of the public methods, but in this case, the unit test of another method loses its informativeness: as it is unclear if the testing method bugs itself or only its private part.
1. Turning private method into the public.
This solution allows us to test the method directly. The serious disadvantage of this approach is that it violates the encapsulation. Another issue we face is dependencies between Do1 and Do2 from PrivateDo. Some mockers allow resolving this issue by partly-mock (e.g., nSubstitude), while others do not have partial mocking functionality.
Nevertheless, we can reduce negative implications by not adding a newly opened method to the interface. For instance:
<code class="hljs cs" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">interface</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ISomeClass</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do1</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>;
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do2</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>;
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>: <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ISomeClass</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do1</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do2</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">PrivateDo</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
}
}</code>
In this case, ISomeClass clients won’t know about PrivateDo. It allows us to avoid illegal usage. The test will not mock the interface ISomeClass, but SomeClass directly (therefore, its methods have become virtual).
2. Reflection.
Reflection allows us to test the private method without breaking encapsulation rules. Nevertheless, the test becomes more complicated, and its performance speed decreases. Some test frameworks include adapters that simplify reflective testing. For example, MsTest has a special class PrivateObject that allows us to call private methods in such a way:
<code class="hljs haxe" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">var</span> someClass = <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">new</span> <span class="hljs-type" style="box-sizing: inherit; color: rgb(208, 135, 112);">SomeClass</span>();
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">var</span> obj = <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">new</span> <span class="hljs-type" style="box-sizing: inherit; color: rgb(208, 135, 112);">PrivateObject</span>(someClass);
obj.Invoke(<span class="hljs-string" style="box-sizing: inherit; color: rgb(163, 190, 140);">"PrivateDo"</span>);</code>
But the problem with the dependency between Do1 and Do2 from PrivateDo won’t be solved using the selected approach.
3. Moving out responsibility.
A wiser solution for the private methods is to make it a public method of another class. For instance:
<code class="hljs lasso" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> class SomeClass: ISomeClass
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> IHelper _helper;
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> SomeClass(IHelper helper)
{
_helper = helper;
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> Do1()
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
_helper.<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">Do</span>();
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-literal" style="box-sizing: inherit; color: rgb(208, 135, 112);">void</span> Do2()
{
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
_helper.<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">Do</span>();
<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">...</span>
}
}</code>
Pros: It solves all the issues with Private method testing and decreases code connection.
Cons: If the private method requires private fields, moving it out to separate type breaks encapsulation. It happens because we need to send private fields as arguments.
This solution is unacceptable if the private method changes the inner state. For instance:
<code class="hljs cpp" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-class" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>:</span> ISomeClass
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">string</span> _privateString1;
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">string</span> _privateString2;
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do1</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do2</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">PrivateDo</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
privateString1 = <span class="hljs-string" style="box-sizing: inherit; color: rgb(163, 190, 140);">"qwe"</span>;
privateString2 = <span class="hljs-string" style="box-sizing: inherit; color: rgb(163, 190, 140);">"asd"</span>;
}
}</code>
In this particular case, we can do a small trick. We can save a private method in a class if we keep only the logic of the change of the state, but move all the rest to the separate class:
<code class="hljs cs" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>: <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">ISomeClass</span>
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">string</span> _privateString1;
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">string</span> _privateString2;
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> IHelper _helper;
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">IHelper helper</span>)</span>
{
_helper = helper;
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do1</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do2</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
PrivateDo();
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">private</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">PrivateDo</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">var</span> result = _helper.Do();
privateString1 = result.String1;
privateString1 = result.String2;
}
}</code>
We can mock classes without an interface. But we need to keep in mind two things. First of all the methods of such classes must be virtual. Otherwise, the part of your mock will work as a mock and another part as an original object. In this case, the lack of proper focus can cause the bug with low traceability. Fixing of such a bug can cost you a few extra hours. An example:
<code class="hljs cpp" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-class" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>
{</span>
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do1</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">virtual</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do2</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do3</span><span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);">()</span>
</span>{
...
}
}</code>
The mocks Do1 and Do2 will work as they are supposed to work. The mock method Do3 is useless because it calls the original method.
The second issue with classes without interfaces is the constructor. The mocking of the class requires the presence of default constructor (without parameters). Otherwise, you need to create mocks of parameters too.
In some cases, the constructor contains the logic. So this logic is called in all tests for mocked classes. It is not a good approach in general. The solution is really simple — all mocked classes must contain the interface. There are no exceptions for this rule.
The only special case is sealed classes without interfaces. We can find them in some frameworks. They are unmockable by default. The only solution is to create a wrapper and mock it.
Logic in a constructor is a well-known antipattern. However, a lot of developers use it. Such an approach has a number of disadvantages. The most crucial for us is that the constructor logic will be called in every test again and again. The bug in the constructor breaks all tests of the class. There are two ways to avoid the issue.
1. Method Init.
It divides the process of initialization into 2 separate parts: object creation (constructor) and additional logic (Init()).
<code class="hljs cs" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">params</span></span>)</span>
{
...
}
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Init</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
}
}</code>
Pros: The tests ignore Init method to avoid dependencies on the constructor logic.
Cons: Outside the tests, this approach works really badly. Firstly, because it is not obvious. Secondly, it complicates the settings of IoC container and violates the Single responsibility principle of SOLID.
I don’t recommend using initializers even though they solve the central issue of constructors.
2. Fabric method.
The instantiation logic is moved to a separate class.
<code class="hljs angelscript" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">SomeClass</span>
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> SomeClass(calculatedParams)
{
...
}
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">interface</span> <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">ISomeClassCreator</span>
{
SomeClass Create(params);
}
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">SomeClassCreator: <span class="hljs-symbol" style="box-sizing: inherit; color: rgb(163, 190, 140);">ISomeClassCreator</span></span>
{
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> SomeClass Create(params)
{
...
}
}</code>
Pros: Allows to get rid of heavy constructors and improves the code structure.
Cons: Requires the creation of two special types for tests. Besides, heavy constructors are the issue not only for unit-testing. That’s why such complication can be reasonable.
If the method uses a certain global resource (for example, settings from app.config) the process of its testing becomes non-trivial at all.
<code class="hljs cs" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">class</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">SomeClass</span>
{
<span class="hljs-function" style="box-sizing: inherit;"><span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">public</span> <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">void</span> <span class="hljs-title" style="box-sizing: inherit; color: rgb(143, 161, 179);">Do</span>(<span class="hljs-params" style="box-sizing: inherit; color: rgb(208, 135, 112);"></span>)</span>
{
...
<span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">var</span> mySetting = ConfigurationManager.AppSettings[<span class="hljs-string" style="box-sizing: inherit; color: rgb(163, 190, 140);">"MySetting"</span>]
...
}
}</code>
Simulating correct operation for ConfigurationManager is a complex task. We can’t mock it. We can create different files for different tests, but this may be a rather complicated solution.
In general, the case is identical to the static object. That’s why the solution is the same — create a class, that contains environment variables and works through it:
<code class="hljs routeros" style="box-sizing: inherit; font-family: monospace, monospace; font-size: 1em; display: block; overflow-x: auto; background-color: rgb(43, 48, 59); color: rgb(192, 197, 206); padding: 30px;">public class SomeClass
{
private<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);"> Settings </span>_settings;
public SomeClass(Settings setting)
{
_<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);"> settings </span>= setting;
}
public void <span class="hljs-keyword" style="box-sizing: inherit; color: rgb(180, 142, 173);">Do</span>()
{
<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">..</span>.
var mySetting = settings.MySetting;
<span class="hljs-built_in" style="box-sizing: inherit; color: rgb(208, 135, 112);">..</span>.
}
}</code>
Support of unit tests adds several factors to consider when designing a testing workflow, but the efforts will pay off.
There are lots of best practices for writing testable code. The main goal for us is to create a robust solution that will be maintainable for everyone. Even if you don’t plan to write unit tests, you need to use this guide because it will help you to improve the quality of the code and the architecture in general. It takes additional time, but it’s worth it.