-->

unit testing a class with event and delegate

2020-02-26 13:05发布

问题:

I am new to testing please help.

I have the following class

public delegate void OnInvalidEntryMethod(ITnEntry entry, string message);

public class EntryValidator
{
    public event OnInvalidEntryMethod OnInvalidEntry;

    public bool IsValidEntry(ITnEntry entry, string ticker)
    {
        if (!IsFieldValid(entry, ticker.Trim().Length.ToString(), "0"))
            return false;

        return true;
    }

    private bool IsFieldValid(ITnEntry entry, string actual, string invalidValue)
    {
        if (actual == invalidValue)
        {
            RaiseInvalidEntryEvent(entry);
            return false;
        }

        return true;
    }

    private void RaiseInvalidEntryEvent(ITnEntry entry)
    {
        if (OnInvalidEntry != null)
            OnInvalidEntry(entry, "Invalid entry in list: " + entry.List.Name + ".");
    }
}

I have written the test case so far but am struggling with the event and delegate as shown below

[TestFixture]
public class EntryValidatorTests
{
    private EntryValidator _entryValidator;

    private FakeTnEntry _selectedEntry;
    private string _ticker;

    [SetUp]
    public void Setup()
    {
        _entryValidator = new EntryValidator();
        _ticker = "BOL";
    }

    private FakeTnEntry MakeEntry(string ticker)
    {
        return new FakeTnEntry { Ticker = ticker};
    }

    [Test]
    public void IsValidEntry_WithValidValues()
    {
        _selectedEntry = MakeEntry(_ticker);

        Assert.IsTrue(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    }

    [Test]
    public void IsValidEntry_WithInValidTicker()
    {
        _selectedEntry = MakeEntry("");
        Assert.IsFalse(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    }
}}

Please can someone help? Thanks..

回答1:

It's probably simplest just to subscribe to the event using an anonymous method:

[Test]
public void IsValidEntry_WithValidValues()
{
    _selectedEntry = MakeEntry(_ticker);
    _entryValidator.OnInvalidEntry += delegate { 
        Assert.Fail("Shouldn't be called");
    };

    Assert.IsTrue(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
}    

[Test]
public void IsValidEntry_WithInValidTicker()
{
    bool eventRaised = false;
    _selectedEntry = MakeEntry("");
    _entryValidator.OnInvalidEntry += delegate { eventRaised = true; };

    Assert.IsFalse(_entryValidator.IsValidEntry(_selectedEntry, _selectedEntry.Ticker));
    Assert.IsTrue(eventRaised);
}

In the second test you might want to validate that the event arguments were as expected too.

Also note that "invalid" is one word - so your test should be IsValidEntry_WithInvalidTicker. I'd also not bother with the setup - I'd just declare new local variables in each test.



回答2:

I would restructure your class to make the RaiseInvalidEntryEvent virtual so it can be mocked in your IsValidEntry_WithInValidTicker and then verified it was called when the ticket was invalid.

Then I would have another test that verified RaiseInvalidEntryEvent called the anon delegate separately.

Unit tests should be as atomic as possible, and you would want to verify both of these behaviors in different tests.

public delegate void OnInvalidEntryMethod(ITnEntry entry, string message);

public class EntryValidator
{
    public event OnInvalidEntryMethod OnInvalidEntry;

    public bool IsValidEntry(ITnEntry entry, string ticker)
    {
        if (!IsFieldValid(entry, ticker.Trim().Length.ToString(), "0"))
            return false;

        return true;
    }

    private bool IsFieldValid(ITnEntry entry, string actual, string invalidValue)
    {
        if (actual == invalidValue)
        {
            RaiseInvalidEntryEvent(entry);
            return false;
        }

        return true;
    }

    public virtual void RaiseInvalidEntryEvent(ITnEntry entry)
    {
        if (OnInvalidEntry != null)
            OnInvalidEntry(entry, "Invalid entry in list: " + entry.List.Name + ".");
    }
}

// Had to reverse engineer the following since they were not available in the question
public interface ITnEntry
{
    Ticket List { get; set; }
    string Ticker { get; set; }
}

public class TnEntry : ITnEntry
{
    public Ticket List { get; set; }
    public string Ticker { get; set; }
}

public class Ticket
{
    public string Name { get; set; }
}

NOTE: Some OOP evangalists have fits when things are declared public instead of private, basically unit testing and TDD have some requirements that pure OOP is at odds with. I've made RaiseInvalidEntryEvent public for simplicity, but normally I would make this internal and then expose the assembly to the unit test via InternalsVisibleTo. I've been doing TDD for the last 4 years now and rarely use private anymore.

And the unit tests would quickly be (note, this is using the MSTEST framework from VS2012)

[TestClass]
public class UnitTest1
{
    #region TestHelpers

    private ITnEntry MakeEntry(string ticker)
    {
        return new TnEntry {Ticker = ticker, List = new Ticket()};
    }

    #endregion

    [TestMethod]
    public void IsValidEntry_WithValidValues_ReturnsTrue()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("BOL");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsTrue(actual);
    }

    [TestMethod]
    public void IsValidEntry_WithInValidTicker_ReturnsFalse()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsFalse(actual);
    }

    [TestMethod]        
    public void IsValidEntry_WithInvalidTicker_RaisesEvent()
    {
        // ARRANGE
        // generate a dynamic mock which will stub all virtual methods
        var target = Rhino.Mocks.MockRepository.GenerateMock<EntryValidator>();
        var selectedEntry = MakeEntry("");

        // ACT
        bool actual = target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        // assert that RaiseInvalidEntryEvent was called
        target.AssertWasCalled(x => x.RaiseInvalidEntryEvent(Arg<ITnEntry>.Is.Anything));
    }

    [TestMethod]
    public void RaiseInvalidEntryEvent_WithValidHandler_CallsDelegate()
    {
        // ARRANGE
        var target = new EntryValidator();
        var selectedEntry = MakeEntry("");
        bool delegateCalled = false;

        // attach a handler to set delegateCalled to true
        target.OnInvalidEntry += delegate 
        {
            delegateCalled = true;
        };

        // ACT
        target.IsValidEntry(selectedEntry, selectedEntry.Ticker);

        // ASSERT
        Assert.IsTrue(delegateCalled);
    }
}


回答3:

Your test should subscribe to the event OnInvalidEntry with a dummy method, call IsValidEntry and check the result.