4

My application is a .NET Core application.

I have a public method as shown below which has two private methods.

   public bool CallService(JObject requestJsonObj, out Status status)
   {
        bool provisioningSuccess = false;
        var preProcessSuccess = PreProcessing(requestJsonObj,out Status status);
        var postProcessSuccess = PostProcessing(requestJsonObj,out Status status);

        if(preProcessSuccess && postProcessSuccess)
        {
              status = Status.ProvisionSuccess;
              provisioningSuccess = true;
        }
        return provisioningSuccess;
   }

Here is Status and private classes

   public enum Status
   {
       [Description("User/Pwd not valid")]
       CredentialInvalid = 111,
       [Description("Provision is success")]
       ProvisionSuccess = 112,
   }

    private PreProcessing(JObject JosnObj, 
        out Status status)
    {
           using (var client = new HttpClient())
           {
                var request = new {.........};
                var response = client.PostAsJsonAsync("api/preprocess", request).Result;
           }
    }

    private PostProcessing(JObject JosnObj, 
        out Status status)
    {
            //..... 
    }

Tried the below way,

     PrivateObject privateHelperObject = new PrivateObject(typeof(MainService));
     actual = (bool)privateHelperObject.Invoke("CallService", requestJsonObj,status);    

It says

The type or namespace name "PrivateObject" could not be found (are you missing a using directive or an assembly reference?)

This is a .NET CORE project. I am not sure if PrivateObject is supported .net core?

kudlatiger
  • 3,028
  • 8
  • 48
  • 98
  • 2
    You do not need to unit test the private method specifically. You write unit tests of the public method to cover all the use cases covered by the code of private methods. That will make sure that the private method code is also tested for its behavior in all the scenarios. – Chetan Jan 10 '20 at 09:12
  • Even if PrivateObject does not seem to be available in .NetCore there's a NuGet Package available for version 2.0 and newer https://www.nuget.org/packages/PrivateObjectExtensions/ – Pumkko Jan 10 '20 at 09:14
  • @ChetanRanpariya , so do I need to scrap the private method and move code to public method? – kudlatiger Jan 10 '20 at 09:17
  • 4
    Use normal reflection? Under the hood `PrivateObject` does nothing different. – MakePeaceGreatAgain Jan 10 '20 at 09:19
  • what I am planning is to throw exceptions from private methods and catch it in public method and assert for exceptions. – kudlatiger Jan 10 '20 at 09:34
  • Why do you want unit test the private method? When testing the public method which in turn will have coverage to the private methods. – Vijayanath Viswanathan Jan 10 '20 at 09:35
  • @VijayanathViswanathan Private method has complex logics like calling a API, validating responses, getting certificate.... so on... internally it calls multiple private methods. – kudlatiger Jan 10 '20 at 09:37
  • You don't need to move the code from private method to public method. You need to write unit tests which will cover execution of the code of private methods.... @kudlatiger – Chetan Jan 10 '20 at 09:45
  • @Pumkko PrivateObjectExtensions seems like an interesting project but it can only access properties and field, not methods. – C.M. Jun 25 '21 at 16:26

5 Answers5

7

You don't need PrivateObject in the first place, as your member to test is public:

var target = new MainService();
var actual = target.CallService(requestJsonObj, status);

That your method itself calls private method doesn't change how you test the public one.

Things get harder if you really need to test the private ones also. So let´s use reflection, which is what PrivateObject does as well under the hood.

var mainServiceObject = new MainService();
var method = mainService.GetType().GetMethod("PreProcessing", BindingFlags.Instance | BindingFlags.NonPublic);

var result = (bool) method.Invoke(mainServiceObject, new[] { requestJsonObj, status });

However be aware that unit-testing private members usually is considered a code smell and often indicates that there are issues with the design - namely that your class is doing too much and should be split into multiple classes each having a single responsibility.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • could you help how do I test without using reflection? I am using NSubstitute. Can I use "Received()" feature of NSubstitute ? – kudlatiger Jan 16 '20 at 08:38
  • I don´t see how that should help you. I´m not even sure what you actually want to achieve. Do you want to **mock** the private methods to do something else? E.g. not performing a web.request but just return a static message? Or do you want to **test** the private method? – MakePeaceGreatAgain Jan 16 '20 at 23:36
  • I want to make sure testing the public method, covers all the private calls. (make sure it reaches and client.PostAsJsonAsync line. so i am trying "received()" feature. – kudlatiger Jan 17 '20 at 02:01
  • You should definitly re-design your class if you really need to do this, as it simply isn´t possible. You can´t mock a private non-virtual method, neither using reflection nor `PrivateObject` nor anything else. Even if you could - maybe there´s some fancy lib out that enables this - you would mock your unit-under-test - so your target-class, which isn´t good. So I think you should extract a new class from the private methods and test that one as well. – MakePeaceGreatAgain Jan 17 '20 at 11:23
6

First of all it is rare when you need to test a private method in unit-tests. Usually you test a unit - a public method that calls all other private (or not) methods. But indeed sometimes there are too much logic inside private method or method is event/message handlers that are not called directly.

Second, as already mentioned, PrivateObject is not a part of a .NET Core/Standard, and I think, it never will be.

As a workaround you could use reflection to call private method in your unit tests:

var mainServiceObject = new MainService();
var method = typeof(MainService).GetMethod("PrivateMethod", BindingFlags.Instance | BindingFlags.NonPublic);
var result = method?.Invoke(mainServiceObject, new object[] { requestJsonObj, status });

P.s.

Please note that in your code CallService is public method, so no need to call it via reflection.

Alexander Goldabin
  • 1,824
  • 15
  • 17
  • PreProcessing and postProcessing are private methods. – kudlatiger Jan 10 '20 at 09:43
  • 1
    Then: var method = typeof(MainService).GetMethod("PreProcessing ", BindingFlags.Instance | BindingFlags.NonPublic); var result = method?.Invoke(mainServiceObject, new object[] { requestJsonObj, status }); – Alexander Goldabin Jan 10 '20 at 09:44
3
var mainService = new MainService(); // This is only to visualise that you need to pass an instance of the class


var dynMethod = typeof(MainService).GetMethod("PreProcessing", BindingFlags.NonPublic | BindingFlags.Instance);

dynMethod.Invoke(mainService , new object[] { methodParams });

// methodParams should be the parameters that this method accept

Azzy Elvul
  • 1,403
  • 1
  • 12
  • 22
1

See https://github.com/microsoft/testfx/issues/366#issuecomment-580147403

You can copy original implementation to your project if needed. https://github.com/microsoft/testfx/tree/main/src/TestFramework/TestFramework.Extensions

cactuaroid
  • 496
  • 1
  • 4
  • 19
0

If changing the scope of the private method to protected is OK, you can make your test class inherit the class owning the private method. Then the test class will be able to call it and unit test it.

A.D.
  • 1,062
  • 1
  • 13
  • 37