Search Unity

Unit Testing private methods in c# - Internals, reflection, etc

Discussion in 'Testing & Automation' started by Darkgaze, Nov 27, 2020.

  1. Darkgaze

    Darkgaze

    Joined:
    Apr 3, 2017
    Posts:
    395
    Hi all.

    Everybody needs to test their little methods and functions. Even when many people say "privates" should not be tested, this is not the case for Unity, specifically when you are creating an Asset that somebody else is going to use, and you have a bunch of tests to see that everything works perfectly everytime you release a version.

    There is an extensive discussion over here: https://stackoverflow.com/questions/9122708/unit-testing-private-methods-in-c-sharp

    Moving all tested methods to public is not a solution, using reflection seems like a pretty hacky solution.
    I also read about using PrivateObject class to access those private methods. But it doesn't seem to be available (at least, right now with Unity 2019.4 and previous)

    Code (CSharp):
    1. Class target = new Class();
    2. PrivateObject obj = new PrivateObject(target);
    3. var retVal = obj.Invoke("PrivateMethod");
    4. Assert.AreEqual(expectedVal, retVal);
    The solution I found most interesting was setting to "internal" all tested private methods, and telling the test assembly to be able to use internals from the assembly I want to test (making them "friend" assemblies), as depicted here : https://improveandrepeat.com/2019/12/how-to-test-your-internal-classes-in-c/

    The problem is that I don't know how to use friend assemblies in Unity:
    https://docs.microsoft.com/en-us/dotnet/standard/assembly/friend

    First I add a:

    Code (CSharp):
    1. [assembly: InternalsVisibleTo("RealtimeTests")]
    where "RealtimeTests" is the name of the other assembly that wants to call the internals declared here. I define this before my namespace definition, and right after the "using" declarations on the file.

    Seems like nothing happens, compilation works but the other file cannot access the internal method.


    Has anybody done this? If I make this work, I'll definitely write an article to help people do this. Testing private methods is key, specially when you have a big maths library that you don't want anybody to use from outside the Asset.
     
    The-Sam and Maeslezo like this.
  2. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,656
    Yes, using
    internal 
    and the
    InternalsVisibleTo 
    attribute is the intended way to do this. We use this a lot in our own code and packages.

    It should just be a question of the name in the InternalsVisibleTo attribute matching the name of your tests assembly. How are you setting up the tests, with an asmdef?
     
    The-Sam likes this.
  3. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    330
    That is the way to go, using internal and InternalsVisibleTo

    You should have a AssemblyInfo.cs file in the root of the asmdef that you want to test
    In a AssemblyInfo.cs, you should include something like
    Code (CSharp):
    1. using System.Runtime.CompilerServices;
    2.  
    3. [assembly:InternalsVisibleTo("OtherAssembly")]
    Imaging you are trying to test a class. This class is belongs to a AssemblyDefinitionX
    You are testing this class in other assembly, let's say AssemblyDefinitionX.Editor.Tests

    So, the way to go is:
    1. In AssemblyDefinitionX.Editor.Tests, you add AssemblyDefinitionX reference
    2. In AssemblyDefinitionX, you add a file named AssemblyInfo.cs
    3. You edit that file, and add [assembly:InternalsVisibleTo("AssemblyDefinitionX.Editor.Tests")]
    Now, you can access to internal AssemblyDefinitionX in AssemblyDefinitionX.Editor.Tests assembly
     
    CodeSmile and Darkgaze like this.
  4. Darkgaze

    Darkgaze

    Joined:
    Apr 3, 2017
    Posts:
    395
    At last I used this. And it worked PERFECTLY. Thanks a lot.

    I added several [assembly... ] lines to the file and it also worked.

    To the list you gave me I would add another obvious point just in case somebody forgets...

    - Add "internal" before the return keyword in the function definition or before the class definition you want to access from outside, it's not needed everywhere. The public ones don't need it, obviously.
     
  5. Darkgaze

    Darkgaze

    Joined:
    Apr 3, 2017
    Posts:
    395
  6. EduardDraude

    EduardDraude

    Joined:
    Mar 8, 2019
    Posts:
    60
    Hmmm, how would you do that for packages downloaded via the PackageManager? For example, I want to access some internal constructor of the Addressables system for testing purposes.
     
  7. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,656
    It's not really possible without modifying the assembly under test to add the InternalsVisibleTo attribute. If I were you I'd probably just use reflection to get and invoke the constructor. It's probably not worth embedding the whole package in your project just so you can add that one attribute to it.
     
    Darkgaze and EduardDraude like this.
  8. EduardDraude

    EduardDraude

    Joined:
    Mar 8, 2019
    Posts:
    60
    Yepp, thanks. So is did ;)
     
  9. denravonska

    denravonska

    Joined:
    Nov 20, 2020
    Posts:
    12
    While InternalsVisibleTo works, it also exposes the previously private variables and methods to the entire assembly definition they reside in. At that point you might as well make them public unless you have a larger use case than tests+implementation.

    What we would need is something like PrivateVisibleTo to retain the encapsulation within the asmdef but allow tests to access private UI elements, for example, to test the UI behavior without having to rewrite to a full blown MVP.