Asked  6 Months ago    Answers:  5   Viewed   27 times

I'm building a class library that will have some public & private methods. I want to be able to unit test the private methods (mostly while developing, but also it could be useful for future refactoring).

What is the correct way to do this?

 Answers

96

If you are using .net, you should use the InternalsVisibleToAttribute.

Tuesday, June 1, 2021
 
Saurabh
answered 6 Months ago
10

Original idea from this answer with a more generic approach. Using a custom DynamicObject as a wrapper for inspecting the value via reflection there was no need to add the InternalsVisibleTo

public class DynamicObjectResultValue : DynamicObject, IEquatable<DynamicObjectResultValue> {
    private readonly object value;

    public DynamicObjectResultValue(object value) {
        this.value = value;
    }

    #region Operators
    public static bool operator ==(DynamicObjectResultValue a, DynamicObjectResultValue b) {
        // If both are null, or both are same instance, return true.
        if (System.Object.ReferenceEquals(a, b)) {
            return true;
        }
        // If one is null, but not both, return false.
        if (ReferenceEquals((object)a, null) || ReferenceEquals((object)b, null)) {
            return false;
        }
        // Return true if the fields match:
        return a.value == b.value;
    }

    public static bool operator !=(DynamicObjectResultValue a, DynamicObjectResultValue b) {
        return !(a == b);
    }
    #endregion

    public override IEnumerable<string> GetDynamicMemberNames() {
        return value.GetType().GetProperties().Select(p => p.Name);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        //initialize value
        result = null;
        //Search possible matches and get its value
        var property = value.GetType().GetProperty(binder.Name);
        if (property != null) {
            // If the property is found, 
            // set the value parameter and return true. 
            var propertyValue = property.GetValue(value, null);
            result = propertyValue;
            return true;
        }
        // Otherwise, return false. 
        return false;
    }

    public override bool Equals(object obj) {
        if (obj is DynamicObjectResultValue)
            return Equals(obj as DynamicObjectResultValue);
        // If parameter is null return false.
        if (ReferenceEquals(obj, null)) return false;
        // Return true if the fields match:
        return this.value == obj;
    }

    public bool Equals(DynamicObjectResultValue other) {
        // If parameter is null return false.
        if (ReferenceEquals(other, null)) return false;
        // Return true if the fields match:
        return this.value == other.value;
    }

    public override int GetHashCode() {
        return ToString().GetHashCode();
    }

    public override string ToString() {
        return string.Format("{0}", value);
    }
}

Assuming the following controller

public class FooController : Controller {

    public IActionResult GetAnonymousObject() {

        var jsonResult = new {
            id = 1,
            name = "Foo",
            type = "Bar"
        };

        return Ok(jsonResult);
    }

    public IActionResult GetAnonymousCollection() {

        var jsonResult = Enumerable.Range(1, 20).Select(x => new {
            id = x,
            name = "Foo" + x,
            type = "Bar" + x
        }).ToList();

        return Ok(jsonResult);
    }
}

Tests could look like

[TestMethod]
public void TestDynamicResults() {
    //Arrange
    var controller = new FooController();

    //Act
    var result = controller.GetAnonymousObject() as OkObjectResult;

    //Assert
    dynamic obj = new DynamicObjectResultValue(result.Value);

    Assert.IsNotNull(obj);
    Assert.AreEqual(1, obj.id);
    Assert.AreEqual("Foo", obj.name);
    Assert.AreEqual(3, obj.name.Length);
    Assert.AreEqual("Bar", obj.type);
}

[TestMethod]
public void TestDynamicCollection() {
    //Arrange
    var controller = new FooController();

    //Act
    var result = controller.GetAnonymousCollection() as OkObjectResult;

    //Assert
    Assert.IsNotNull(result, "No ActionResult returned from action method.");
    dynamic jsonCollection = result.Value;
    foreach (dynamic value in jsonCollection) {
        dynamic json = new DynamicObjectResultValue(value);

        Assert.IsNotNull(json.id,
            "JSON record does not contain "id" required property.");
        Assert.IsNotNull(json.name,
            "JSON record does not contain "name" required property.");
        Assert.IsNotNull(json.type,
            "JSON record does not contain "type" required property.");
    }
}
Saturday, June 19, 2021
 
Hilmi
answered 6 Months ago
30
  • Write just enough of a test for the test to fail, usually this is because your code does not compile
  • write just enough code to make your test pass
  • repeat

These are the rules of TDD. This means that you are only ever writing one unit test and one bit of code at a time and doing this iteratively.

The point of TDD is not writing tests, it is using your tests to drive out the design. The idea is that once all your tests pass and they cover all the functionality you know that your code is also complete. Without tests, how do you know when you are done. So, when you feel that you have tested everything, stop.

Saturday, August 14, 2021
 
tpow
answered 4 Months ago
54

I'll answer my own question. After a bit of reading and thinking, I believe I shouldn't be unit testing these private methods. I should just test the public interface. If the private methods that do the internal processing are important enough to test independently and are not just coincidences of the current implementation, then perhaps this is a sign that they should be refactored out into a separate class.

Wednesday, September 22, 2021
 
JCm
answered 2 Months ago
JCm
21

One solution is to not have the constructor do the work:

public class PurchaseOrder
{
    public PurchaseOrder(string newNumber, string newLine)
    {
        NewNumber = newNumber;
        NewLine = newLine;
    }
    // ...
}

Then testing is easy and isolated - you're not testing GetNewNumber and GetNewLine at the same time.

To help using PurchaseOrder you can create a factory method that puts it together:

public static PurchaseOrder CreatePurchaseOrder(string purchaseOrderText)
{
   return new PurchaseOrder(
     GetNewNumber(purchaseOrderText),
     GetNewLine(purchaseOrderText));
}
Saturday, November 6, 2021
 
danjah
answered 3 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share