NUnit Mocking не работает для метода Singleton - PullRequest
2 голосов
/ 21 октября 2009

Терпите меня, я новичок в NUnit. Я родом из земли Рельсов, поэтому кое-что из этого является новым для меня.

У меня есть строка кода, которая выглядит следующим образом:

var code = WebSiteConfiguration.Instance.getCodeByCodeNameAndType("CATALOG_Brands_MinQty", item.Catalog);

Я пытаюсь поиграть, вот так (предположим, code уже инициализирован):

var _websiteConfigurationMock = new DynamicMock(typeof(WebSiteConfiguration));
_websiteConfigurationMock.ExpectAndReturn("getCodeByCodeNameAndType", code);

Когда я отлаживаю тест, getCodeByCodeNameAndType возвращает null вместо ожидаемого code. Что я делаю не так?

NUnit версия: 2.2.8

Ответы [ 3 ]

7 голосов
/ 21 октября 2009

Извините, но я никогда не использовал NUnit.Mocks - но у меня есть некоторый опыт работы с NMock и Moq [что, кстати, я очень рекомендую]. Как правило, вы используете фиктивную библиотеку для генерации прокси для определений интерфейса, и я предполагаю, что NUnit.Mocks работает так же.

Поэтому, если вы хотите издеваться над своим синглтоном, вам, вероятно, придется сделать следующее:

а. Создайте интерфейс, скажем

// All methods you would like to mock from this class, should 
// be members of this interface
public interface IWebSiteConfiguration
{
    // Should match signature of method you are mocking
    CodeType getCodeByCodeNameAndType (
        string codeString, 
        CatalogType catalogType);
}

б. Интерфейс "Реализация"

// You've already written the method, interface matches signature,
// should be as easy as slapping interface on class declaration
public class WebSiteConfiguration : IWebSiteConfiguration { }

с. Интерфейс потребления

хорошо, так что шаг c. где большая часть вашей работы будет. По логике вещей, если вы издеваетесь над своим синглтоном, вы на самом деле проводите модульное тестирование потребителя [которое вы исключили из своего образца]. Для ц. просто добавьте параметр в ctor потребителя или добавьте общедоступное свойство типа 'IWebSiteConfiguration', а затем внутренне обратитесь к члену экземпляра и вызовите ваши методы для этого нового интерфейса. Учтите это,

был

public class MyClass
{
    public MyClass () { }

    public void DoSomething ()
    {
        // bad singleton! bad boy! static references are bad! you
        // can't change them! convenient but bad!
        code = WebSiteConfiguration.Instance.getCodeByCodeNameAndType (
            "some.string", 
            someCatalog)
    }
}

становится

public class MyClass
{
    private readonly IWebSiteConfiguration _config = null;

    // just so you don't break any other code, you can default
    // to your static singleton on a default ctor
    public MyClass () : this (WebSiteConfiguration.Instance) { }

    // new constructor permits you to swap in any implementation
    // including your mock!
    public MyClass (IWebSiteConfiguration config) 
    {
        _config = config;
    }

    public void DoSomething ()
    {
        // huzzah!
        code = _config.getCodeByCodeNameAndType ("some.string", someCatalog)
    }
}

В своем модульном тесте создайте макет, передайте ссылку на макет потребителю и протестируйте потребителя.

[Test]
public void Test ()
{
    IWebSiteConfiguration mockConfig = null;
    // setup mock instance and expectation via
    // NUnit.Mocks, NMock, or Moq

    MyClass myClass = new MyClass (mockConfig);
    myClass.DoSomething ();

    // verify results
}

Это также служит практическим введением в Dependency Injection [DI]. Это просто практика передачи или «внедрения» ссылок на службы [например, класс конфигурации вашего веб-сайта] потребителю, вместо того, чтобы потребитель вызывал службу напрямую [например, через статический одноэлементный класс].

Надеюсь, это поможет:)

1 голос
/ 09 марта 2012

Кажется, для этого есть своеобразное решение с помощью рефлексии, или, может быть, я совершенно не понял этого.

Это обсуждается здесь: http://www.geekbeing.com/2010/05/23/how-to-unit-test-singleton-hack-in-c

Может ли это действительно работать?

public class TestableSingleton : SingletonClass
{
  public TestableSingleton ()
  {
    FieldInfo fieldInfo = typeof(SingletonClass)
        .GetField("_instance",
        BindingFlags.Static | BindingFlags.NonPublic);
    fieldInfo.SetValue(Instance, this);
  }
}

Проект доступен на https://github.com/rbabreu/TestableSingleton

На самом деле я не смог скомпилировать его в Visual Studio, поскольку у SingletonClass был бы приватный конструктор. Если кто-то заставит его работать, было бы здорово избежать накладных расходов на шаблон адаптера.

1 голос
/ 21 октября 2009

DynamicMock создает новый объект в памяти, который представляет интерфейс, или маршаллируемый (наследующий от MarshalByRef) класс, который вы хотите смоделировать.

Попробуйте это:

var _websiteConfigurationMock = new DynamicMock(typeof(WebSiteConfiguration));
_websiteConfigurationMock.ExpectAndReturn("getCodeByCodeNameAndType", code);
WebSiteConfiguration conf = (WebSiteConfiguration)_websiteConfigurationMock.MockInstance;
var x = conf.getCodeByCodeNameAndType("CATALOG_Brands_MinQty", item.Catalog);

Обратите внимание, что третья строка не будет работать, если WebSiteConfiguration не наследуется от MarshalByRef.

Что вы обычно делаете, это макетируете интерфейс и получаете новый объект, который реализует этот интерфейс, но ведет себя так, как вы его настроили, без необходимости идти и делать для него конкретный тип, так что я не полностью уверен, что то, что вы делаете, будет работать, если вы не используете более эффективную среду изоляции, такую ​​как TypeMock, которая может перехватывать вызовы статических методов / свойств в существующих объектах.

...