简体   繁体   中英

How can I fake call to current class' method with NSubstitute?

I'm new to NSubstitue (and quite new to unit testing in .NET at all). I want to test if my class saves all data in different files for each entry in eg StringDictionary.

Say I have my class DataManipulation.cs :

using System;
using System.Collections;
using System.Collections.Specialized;

namespace ApplicationName
{
    // interface for NSubstitute
    public interface IManipulator
    {
        void saveAllData();
        void saveEntry(string entryKey, string entryValue);
    }

    public class DataManipulator : IManipulator
    {
        protected StringDictionary _data {get; private set;}

        public DataManipulator()
        {
            _data = new StringDictionary();
        }

        public void addData(string name, string data)
        {
            this._data.Add(name, data);
        }

        public void saveAllData()
        {
            // potential implementation - I want to test this
            foreach (DictionaryEntry entry in this._data)
            {
                this.saveEntry(entry.Key.ToString(), entry.Value.ToString());
            }
        }

        public void saveEntry(string entryKey, string entryValue)
        {
            // interact with filesystem, save each entry in its own file
        }
    }
}

What I want to test: when I call DataManipulator.saveAllData() it saves each _data entry in a separate file - meaning it runs saveEntry number of times that equals to _data.Count . Is it possible with NSubstitute?

Each time I try to use DataManipulation as tested object and separately as a mock - when I run Received() I have info that no calls were made.

NUnit test template I want to use:

using System;
using System.Collections.Generic;

using NUnit.Framework;
using NSubstitute;

namespace ApplicationName.UnitTests
{
    [TestFixture]
    class DataManipulatorTests
    {
        [Test]
        public void saveAllData_CallsSaveEntry_ForEachData()
        {

            DataManipulator dm = new DataManipulator();
            dm.addData("abc", "abc");
            dm.addData("def", "def");
            dm.addData("ghi", "ghi");

            dm.saveAllData();

            // how to assert if it called DataManipulator.saveEntry() three times?
        }

    }
}

Or should I do it in different way?

According to some OOP principles and testing needs you have to introduce a dependency or some construction to create "seam" which will fit good for testing.

Using of another dependency as a mock

It will encapsulate data storage and you will check your assertions against it. I recommend you to read about what's the difference between fake, stub and mock.

  1. Add new storage interface and implementation.

     public interface IDataStorage { void Store(string key, string value); } public class DataStorage : IDataStorage { public void Store(string key, string value) { //some usefull logic } } 
  2. Use it as dependency (and inject via constructor) in your Manipulator implementation

     public class DataManipulator : IManipulator { protected IDataStorage _storage { get; private set; } protected StringDictionary _data { get; private set; } public DataManipulator(IDataStorage storage) { _storage = storage; _data = new StringDictionary(); } public void addData(string name, string data) { this._data.Add(name, data); } public void saveAllData() { // potential implementation - I want to test this foreach (DictionaryEntry entry in this._data) { this.saveEntry(entry.Key.ToString(), entry.Value.ToString()); } } public void saveEntry(string entryKey, string entryValue) { _storage.Store(entryKey, entryValue); } } 
  3. Test it

     [Test] public void saveAllData_CallsSaveEntry_ForEachData() { var dataStorageMock = Substitute.For<IDataStorage>(); DataManipulator dm = new DataManipulator(dataStorageMock); dm.addData("abc", "abc"); dm.addData("def", "def"); dm.addData("ghi", "ghi"); dm.saveAllData(); dataStorageMock.Received().Store("abc", "abc"); dataStorageMock.Received().Store("def", "def"); dataStorageMock.Received().Store("ghi", "ghi"); //or dataStorageMock.Received(3).Store(Arg.Any<string>(), Arg.Any<string>()); } 

Most important here that you have not to test private method call. It's a bad practice! Unit testing is all about of testing of public contract, not private methods, which are more changeable in time. (Sorry, I miss that saveEntry(..) is public)

Using of DataManipulator as a mock

I think it's not a good idea, but... The only way to do that with NSubstitute is to make method saveEntry virtual:

public virtual void saveEntry(string entryKey, string entryValue)
{
//something useful
}

and test it:

[Test]
public void saveAllData_CallsSaveEntry_ForEachData()
{

    var dm = Substitute.For<DataManipulator>();
    dm.addData("abc", "abc");
    dm.addData("def", "def");
    dm.addData("ghi", "ghi");

    dm.saveAllData();

    dm.Received(3).saveEntry(Arg.Any<string>(), Arg.Any<string>());
}

The need to do some method virtual just for testing needs may be not very attractive but..

  1. As soon as your tests are also the clients of your business logic, one can take it.
  2. It is possible to use some "heavy" testing frameworks like MS Fakes in this certain case, but it seems to be an overkill.
  3. Another solution is to test another unit of work, which covers depicted one (and probably looks like my first solution).

UPD: read it http://nsubstitute.github.io/help/partial-subs/ for better understanding of NSubstitute.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM