{"id":1383,"date":"2024-12-08T00:00:00","date_gmt":"2024-12-07T23:00:00","guid":{"rendered":"https:\/\/demo.sidekick.be\/itenium\/testing-internals\/"},"modified":"2025-11-28T13:31:41","modified_gmt":"2025-11-28T12:31:41","slug":"testing-internals","status":"publish","type":"post","link":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/","title":{"rendered":"Testing Internal Methods"},"content":{"rendered":"<p>The tests are your safety net you want to rely on!<\/p>\n<h1 id=\"when\">When<\/h1>\n<p>There are actually many cases where you want to do this:<\/p>\n<h2 id=\"legacy-singletons\">Legacy Singletons<\/h2>\n<blockquote>\n<p><strong>The Legacy Code Dilemma<\/strong><br \/> When we have to change code, we should have tests in place.<br \/> To put tests in place, we often have to change code.<\/p>\n<\/blockquote>\n<p>Your old legacy codebase has something like:<br \/> (yeah, it\u2019s probably a lot more convoluted than this)<\/p>\n<pre><code class=\"language-c#\">public class Singleton\n{\n  public static readonly Singleton Instance = new();\n  private Singleton() { }\n}\n<\/code><\/pre>\n<h3 id=\"refactored-testable-singleton\">Refactored Testable Singleton<\/h3>\n<p>We introduce an interface, mimicking the original API and provide an <code>internal<\/code> setter so that we can substitute it with something else during testing!<\/p>\n<pre><code class=\"language-c#\">public class Singleton : ISingleton\n{\n  private static ISingleton _instance = new Singleton();\n\n  public static ISingleton Instance\n  {\n    get =&gt; _instance;\n    \/\/ We introduced a \"seam\"!\n    internal set =&gt; _instance = value;\n  }\n\n  private Singleton() {}\n}\n<\/code><\/pre>\n<h2 id=\"limiting-the-api-surface\">Limiting the API surface<\/h2>\n<p>Inside project X you have some helper classes\/functions, a few Extension Methods, or maybe part of your startup code has some logic in it.<\/p>\n<p>These are all <code>internal<\/code> because you like your projects to have a clean public API, exposing only those things that external users are interested in, and hide everything else.<\/p>\n<p>You don\u2019t want to make them public, but obviously you do want to write tests for them!<\/p>\n<h2 id=\"when-not\">When Not<\/h2>\n<pre><code class=\"language-c#\">public class Calculator\n{\n  public Result Calculate()\n  {\n    var step1 = Step1();\n    var step2 = Step2(step1);\n    return Step3(step2);\n  }\n\n  internal object Step1() {}\n  internal object Step2(object data) {}\n  internal Result Step3(object data) {}\n}\n<\/code><\/pre>\n<p>I\u2019ve seen code like this. Instead of testing the public <code>Calculate<\/code> method only, there are also UnitTests for the sub-algorithms, methods which have been made <code>internal<\/code> only for those UnitTests.<br \/> Maybe even mock out the <code>Step1-3<\/code> when testing the public method \ud83d\ude32<\/p>\n<p>It\u2019s almost always a bad idea; as your implementation and your tests are now very strongly tied together. It\u2019s become hard to change the sub-parts. Worse, if the requirements change, <code>Step1-3<\/code> might not make much sense anymore but you\u2019re probably going to endure anyway rather than rewrite all those tests\u2026<\/p>\n<h1 id=\"how\">How<\/h1>\n<p>This blog post was supposed to be just this but, well, I got carried away\u2026<\/p>\n<h2 id=\"old-legacy-project-format\">Old Legacy Project Format<\/h2>\n<p>Create an <code>AssemblyInfo.cs<\/code> class and add:<\/p>\n<pre><code class=\"language-c#\">using System.Runtime.CompilerServices;\n[assembly: InternalsVisibleTo(\"Company.Project.UnitTests\")]\n<\/code><\/pre>\n<h2 id=\"new-sdk-style-project-format\">New SDK-Style Project Format<\/h2>\n<p>Add to the <code>csproj<\/code> file:<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;AssemblyAttribute Include=\"System.Runtime.CompilerServices.InternalsVisibleTo\"&gt;\n    &lt;_Parameter1&gt;Company.Project.UnitTests&lt;\/_Parameter1&gt;\n  &lt;\/AssemblyAttribute&gt;\n&lt;\/ItemGroup&gt;\n<\/code><\/pre>\n<h1 id=\"summary\">Summary<\/h1>\n<p>I could easily remember the <code>AssemblyInfo<\/code> version but the <code>csproj<\/code> one\u2026 not so much.<\/p>\n<p>Ready for copy-pasta now!<\/p>\n<p>Somehow I ended up writing about \u201chigh cohesion\u201d, \u201cWorking Effectively with Legacy Code\u201d and \u201cencapsulation\u201d instead!<\/p>\n<h1>Extras<\/h1>\n<p><b>Stuff that came into being during the making of this post<\/b><\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/itenium-be\/DotNetUnitTestFrameworks\">Example UnitTests in NUnit, xUnit and MSTest<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Typically you want to write tests only for <code>public<\/code> methods. You want to avoid writing tests for private methods so that when you change the implementation, your tests remain green and you can do that ruthless refactoring without having to worry about introducing new bugs.<\/p>\n","protected":false},"author":1,"featured_media":1977,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[11],"tags":[21],"class_list":["post-1383","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","tag-testing"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Testing Internal Methods - Itenium<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Testing Internal Methods - Itenium\" \/>\n<meta property=\"og:description\" content=\"Typically you want to write tests only for public methods. You want to avoid writing tests for private methods so that when you change the implementation, your tests remain green and you can do that ruthless refactoring without having to worry about introducing new bugs.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/\" \/>\n<meta property=\"og:site_name\" content=\"Itenium\" \/>\n<meta property=\"article:published_time\" content=\"2024-12-07T23:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-28T12:31:41+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png\" \/>\n\t<meta property=\"og:image:width\" content=\"360\" \/>\n\t<meta property=\"og:image:height\" content=\"300\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Usdevelop\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Usdevelop\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/\",\"url\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/\",\"name\":\"Testing Internal Methods - Itenium\",\"isPartOf\":{\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png\",\"datePublished\":\"2024-12-07T23:00:00+00:00\",\"dateModified\":\"2025-11-28T12:31:41+00:00\",\"author\":{\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/6ab401ce01e858184ee4a8667fe0f022\"},\"breadcrumb\":{\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage\",\"url\":\"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png\",\"contentUrl\":\"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png\",\"width\":360,\"height\":300,\"caption\":\"testing the internals\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/demo.sidekick.be\/itenium\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Testing Internal Methods\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/#website\",\"url\":\"https:\/\/demo.sidekick.be\/itenium\/\",\"name\":\"Itenium\",\"description\":\"We build software. We build careers.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/demo.sidekick.be\/itenium\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/6ab401ce01e858184ee4a8667fe0f022\",\"name\":\"Usdevelop\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/cc8cf9b9e9d5bbe7e8d247907cc9ac8457bbfa2854b52eaddf6529adfe88ceab?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/cc8cf9b9e9d5bbe7e8d247907cc9ac8457bbfa2854b52eaddf6529adfe88ceab?s=96&d=mm&r=g\",\"caption\":\"Usdevelop\"},\"sameAs\":[\"https:\/\/demo.sidekick.be\/itenium\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Testing Internal Methods - Itenium","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_US","og_type":"article","og_title":"Testing Internal Methods - Itenium","og_description":"Typically you want to write tests only for public methods. You want to avoid writing tests for private methods so that when you change the implementation, your tests remain green and you can do that ruthless refactoring without having to worry about introducing new bugs.","og_url":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/","og_site_name":"Itenium","article_published_time":"2024-12-07T23:00:00+00:00","article_modified_time":"2025-11-28T12:31:41+00:00","og_image":[{"width":360,"height":300,"url":"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png","type":"image\/png"}],"author":"Usdevelop","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Usdevelop","Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/","url":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/","name":"Testing Internal Methods - Itenium","isPartOf":{"@id":"https:\/\/demo.sidekick.be\/itenium\/#website"},"primaryImageOfPage":{"@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage"},"image":{"@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage"},"thumbnailUrl":"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png","datePublished":"2024-12-07T23:00:00+00:00","dateModified":"2025-11-28T12:31:41+00:00","author":{"@id":"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/6ab401ce01e858184ee4a8667fe0f022"},"breadcrumb":{"@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#primaryimage","url":"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png","contentUrl":"https:\/\/demo.sidekick.be\/itenium\/wp-content\/uploads\/2025\/11\/internals.png","width":360,"height":300,"caption":"testing the internals"},{"@type":"BreadcrumbList","@id":"https:\/\/demo.sidekick.be\/itenium\/blog\/dotnet\/testing-internals\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/demo.sidekick.be\/itenium\/"},{"@type":"ListItem","position":2,"name":"Testing Internal Methods"}]},{"@type":"WebSite","@id":"https:\/\/demo.sidekick.be\/itenium\/#website","url":"https:\/\/demo.sidekick.be\/itenium\/","name":"Itenium","description":"We build software. We build careers.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/demo.sidekick.be\/itenium\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/6ab401ce01e858184ee4a8667fe0f022","name":"Usdevelop","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/demo.sidekick.be\/itenium\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/cc8cf9b9e9d5bbe7e8d247907cc9ac8457bbfa2854b52eaddf6529adfe88ceab?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/cc8cf9b9e9d5bbe7e8d247907cc9ac8457bbfa2854b52eaddf6529adfe88ceab?s=96&d=mm&r=g","caption":"Usdevelop"},"sameAs":["https:\/\/demo.sidekick.be\/itenium"]}]}},"_links":{"self":[{"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/posts\/1383","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/comments?post=1383"}],"version-history":[{"count":3,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/posts\/1383\/revisions"}],"predecessor-version":[{"id":2255,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/posts\/1383\/revisions\/2255"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/media\/1977"}],"wp:attachment":[{"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/media?parent=1383"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/categories?post=1383"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demo.sidekick.be\/itenium\/wp-json\/wp\/v2\/tags?post=1383"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}