Как вызвать событие делегата, используя параметр ref в NSubstitute 3.1?

Я работаю над проектом С#, в котором используется сторонняя библиотека. Эта библиотека определяет довольно необычное событие делегата, используя параметр ref:

event GetDataHandler OnGetData;
public delegate bool GetDataHandler(string name, ref byte[] data);

Я пытаюсь вызвать это событие в модульном тесте через NSubstitute (версия 3.1), но не могу заставить его работать. Я попробовал этот код (и в основном все его варианты, которые я мог придумать):

var theKey = "test";
byte[] theData = null;
_theObject.OnGetData += Raise.Event<GetDataHandler>(theKey, ref theData);

Но это не компилируется. Компилятор сообщает: Аргумент 2 нельзя передавать с ключевым словом 'ref'.

Я знаю, что механизм out/ref изменился в NSubstitute 4.x, но моя компания еще не обновилась до более новой версии.

Есть ли способ запустить это с помощью NSubstitute 3.1? Большое спасибо!

С уважением, Оливер


person Baldewin    schedule 19.08.2019    source источник


Ответы (1)


Эта перегрузка Raise.Event принимает параметры как params object[]. Мы можем передать массив байтов byref в качестве стандартного значения в этом массиве params (что означает, что мы не обеспечиваем безопасность времени компиляции для передаваемых нами аргументов события, но тест довольно быстро обнаружит это, если мы ошибемся :) ):

_theObject.OnGetData += Raise.Event<GetDataHandler>("name", theData);

Вот исполняемый пример:

using NSubstitute;
using Xunit;

public delegate bool GetDataHandler(string name, ref byte[] data);
public interface ISomeType {
    event GetDataHandler OnGetData;
}
public class SampleFixture {

    string lastNameUsed = "";
    byte[] lastBytesUsed = new byte[0];

    [Fact]
    public void SomeTest() {
        var sub = Substitute.For<ISomeType>();
        var data = new byte[] { 0x42 };           
        sub.OnGetData += Sub_OnGetData;

        sub.OnGetData += Raise.Event<GetDataHandler>("name", data);

        Assert.Equal("name", lastNameUsed);
        Assert.Equal(data, lastBytesUsed);
    }

    private bool Sub_OnGetData(string name, ref byte[] data) {
        lastNameUsed = name;
        lastBytesUsed = data;
        return true;
    }
}

Редактировать после получения дополнительной информации в комментарии.

Я не думаю, что NSubstitute поддерживает проверку значения, которое возвращается в этом случае.

Не зная точно, что вы пытаетесь протестировать, я могу предложить несколько общих подходов к тестированию подобных вещей.

Первый вариант — передать код тестового двойника (в данном случае реализацию ISomeType), над которым у вас есть полный контроль. Если интерфейс не огромен, я бы рекомендовал этот подход.

Другой вариант — протестировать делегата и проводку по отдельности. Например, для этого класса:

public class ClassUnderTest {
    public ClassUnderTest(ISomeType dep) {
        dep.OnGetData += Dep_OnGetData;
    }

    public static bool Dep_OnGetData(string name, ref byte[] data) {
        data = System.Text.Encoding.UTF8.GetBytes(name);
        return true;
    }
}

Мы можем протестировать делегат независимо, а затем проверить, подключили ли мы этот делегат:

[Fact]
public void TestDelegate() {
    byte[] data = new byte[0];
    var result = ClassUnderTest.Dep_OnGetData("hi", ref data);

    Assert.True(result);
    Assert.Equal(new byte[] { 104, 105 }, data);
}

[Fact]
public void TestWireup() {
    var sub = Substitute.For<ISomeType>();
    var subject = new ClassUnderTest(sub);

    sub.Received().OnGetData += ClassUnderTest.Dep_OnGetData;
}

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

В-третьих, мы можем использовать более проверяемую оболочку над рассматриваемой библиотекой. Или этот тест может быть совершенно не на том уровне — будьте осторожны с насмешливыми типами, которыми мы не владеем. (Я немного написал об этом здесь.)

Если вы можете предоставить немного больше информации о том, что вы пытаетесь протестировать в этом сценарии, я буду рад попытаться найти более разумный ответ. :)

person David Tchepak    schedule 19.08.2019
comment
Привет Дэвид, спасибо за ваш ответ. Проблема, однако, в том, что реальный обработчик событий в тестируемом классе присваивает значение data. Я думаю, именно поэтому разработчик сторонней библиотеки в первую очередь использовал параметр ref (хотя параметр out, вероятно, был бы лучше). Хотя, конечно, можно вызвать событие без ключевого слова ref, тогда я не могу проверить, назначил ли мой обработчик событий правильные данные для data. - person Baldewin; 20.08.2019
comment
Привет @Baldewin, спасибо за разъяснение. Я добавил некоторые другие варианты ответа. Не идеально, я знаю, но надеюсь, что это немного поможет. - person David Tchepak; 20.08.2019
comment
Привет @David, большое спасибо. Мне нравится идея тестирования делегата отдельно! - person Baldewin; 20.08.2019