Baldewin
Baldewin

Reputation: 1653

How to raise a delegate event using a ref parameter in NSubstitute 3.1?

I'm working on a C# project that uses a 3rd party library. This library defines a rather unusual delegate event using a ref parameter:

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

I'm trying to raise this event in a unit test via NSubstitute (version 3.1) but I can't get it to work. I tried this code (and basically every variation of it that I could think of):

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

But this does not compile. The compiler says: Argument 2 may not be passed with the 'ref' keyword.

I'm aware that the out/ref mechanism has changed with NSubstitute 4.x but my company has not upgraded to the newer version yet.

Is there any way to get this up and running using NSubstitute 3.1? Thanks a lot!

Best regards, Oliver

Upvotes: 0

Views: 352

Answers (1)

David Tchepak
David Tchepak

Reputation: 10484

That Raise.Event overload takes parameters as a params object[]. We can pass the byref byte array as a standard value in this params array (which means we don't get compile-time safety for the event args we're passing, but the test will pick this up pretty quickly if we get it wrong :) ):

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

Here's an executable example:

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;
    }
}

Edit after more info provided in comment.

I don't think NSubstitute supports inspecting the value that comes back in this case.

Without knowing exactly what you're trying to test, I can suggest a couple of general approaches for testing this sort of thing.

First option is to hand code a test double (in this case an implementation of ISomeType) that you have full control over. Unless the interface is huge, I'd recommend this approach.

Another option is to test the delegate and the wire-up separately. For example, given this class:

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;
    }
}

We can test the delegate independently, and then test we've hooked up that delegate:

[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;
}

I think the delegate test in this case is potentially very useful, but the wire-up test is probably not great because it is very specific to the particular implementation, rather than the behaviour/outcome required. But in this case observing the particular effect is difficult, so it is a potential answer.

Thirdly, we may be able to use a more testable wrapper over the library in question. Or this test may be at the wrong level entirely -- be wary of mocking types we don't own. (I've written a bit about this here.)

If you can provide a little more info on what you're trying to test in this scenario I'm happy to try to come up with a more reasonable answer. :)

Upvotes: 1

Related Questions