Jess
Jess

Reputation: 25029

How to Verify (VerifyTests) a private member of a class

I want to Verify a class, but it does not have any public members. Here is the class:

public class SpreadsheetValuesList
{
    private readonly List<SpreadsheetValue> _spreadsheetValues = new();

    public SpreadsheetValuesList(string json)
    {
        _spreadsheetValues = JsonSerializer.Deserialize<List<SpreadsheetValue>>(json ?? "[]", new JsonSerializerOptions());
    }

    public void Add(SpreadsheetValue spreadsheetValue)
    {
        _spreadsheetValues.Add(spreadsheetValue);
    }

    public string ToJson()
    {
        return JsonSerializer.Serialize(_spreadsheetValues);
    }   
}

When I call verify on this object in a MSTest, I get this:

enter image description here

The Verify documentation does not seem to have anything about this, nor Stack Overflow. So I tried a few things:

  1. I (naively) made an override ToString method thinking that maybe Verify would call it and put it in the Test.received.txt file. It did not.
  2. I searched the github repo thinking maybe there is an attribute I can use, but no.
  3. I tried TreatAsString, but this does not work either. If you read the fine print in the doc it says when the object is passed "directly" to verify. I am not doing that. I am only verifying it as a component of another object.
  4. If I expose the List as a public property public List<SpreadsheetValue> Values { get; } = new(); then it appears in the received.txt file like this:

enter image description here

This is what I want, but I do not want to break the encapsulation of my class just so I can get a test to work. Is there a way to provide a method or attribute on a public method so Verify can output the data to the received/verified file?

This class is used as a component of other classes, so I am calling Verify on the larger class like this:

// Assert
await Verify(viewModel);

Upvotes: 1

Views: 241

Answers (1)

Greg Burghardt
Greg Burghardt

Reputation: 18783

Typically you do not write unit tests for private members or methods. Instead, write tests for the public-facing behavior of your class. Some ideas that fit the philosophy of unit testing:

  1. Write unit tests for the ToJson() method. This might require some code gymnastics to format the expected JSON in code, but remove unnecessary formatting when comparing your expected output to the actual output.

  2. Expose a public IEnumerable<SpreadsheetValue> property:

    public IEnumerable<SpreadsheetValue> Items => _spreadsheetValues;
    
  3. Since your SpreadsheetValuesList is logically a list, this class can implement IEnumerable<SpreadsheetValue> directly:

    public class SpreadsheetValuesList : IEnumerable<SpreadsheetValue>
    {
        private readonly List<SpreadsheetValue> _spreadsheetValues = new();
    
        public SpreadsheetValuesList(string json)
        {
            _spreadsheetValues = JsonSerializer.Deserialize<List<SpreadsheetValue>>(json ?? "[]", new JsonSerializerOptions());
        }
    
        // Add() and ToJson() methods omitted for brevity
    
        #region IEnumerable<T> interface
    
        System.Collections.IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    
        IEnumerator<SpreadsheetValue> GetEnumerator()
        {
            return _spreadsheetValues.GetEnumerator();
        }
    
        #endregion
    }
    

The nice thing about option 3 is you have a functionally complete class that you can use in a foreach loop, or use with LINQ queries:

var values = new SpreadsheetValuesList("some JSON");

foreach (var value in values)
{
    // do something with value
}

This makes asserting the internals feel a little more idiomatic for C#:

var values = new SpreadsheetValuesList("some JSON");

Assert.AreEqual(2, values.Count());
Assert.AreEqual("Baz", values.First().Foo);

Don't be afraid to add additional readonly properties for other intersting internal information, like the total number of items:

public int Count => _spreadsheetValues.Count;

The main thing is to never expose the data in your class in a way that allows the outside world to mutate that data unpredictably. This keeps your tests (and code) deterministic.

Read How do you unit test private methods? for an interesting SoftwareEngineering.SE post about this same subject.

Upvotes: 1

Related Questions