Frage

Ich habe eine Klasse, die abonniert zu einer Veranstaltung über PRISMEN Ereignis-Aggregator.

Wie es etwas schwierig ist, den Ereignis-Aggregator zu verspotten wie erwähnt hier , ich instanziiert nur eine echte und an das zu testende System übergeben.

In meinem Test veröffentliche ich dann das Ereignis über diesen Aggregator und dann überprüfen, wie mein System im Test reagiert darauf. Da die Veranstaltung wird von einem Filesystemwatcher während der Produktion erhöht werden, möchte ich durch Zeichnung auf dem UIThread Verwendung des automatischen Versands machen, so kann ich meine UI aktualisieren, sobald das Ereignis ausgelöst wird.

Das Problem ist, dass während des Tests, das Ereignis nie in dem getesteten System bemerkt wird, wenn ich auf der UIThread nicht abonnieren.

Ich bin mit MSpec für meine Tests, die ich von innen VS2008 laufen über TDD.Net. Hinzufügen [RequiresSta] meine Testklasse hilft nicht

Hat jemand eine Lösung haben, die mich erspart Änderung der ThreadOption während meiner Tests (beispielsweise über eine Eigenschaft - was eine hässliche Hack) ???

War es hilfreich?

Lösung

Wenn Sie sowohl das Ereignis und das Ereignis Aggregator verspotten und Rückruf der Verwendung moq, können Sie sie tun.

Hier ist ein Beispiel:

Mock<IEventAggregator> mockEventAggregator;
Mock<MyEvent> mockEvent;

mockEventAggregator.Setup(e => e.GetEvent<MyEvent>()).Returns(mockEvent.Object);

// Get a copy of the callback so we can "Publish" the data
Action<MyEventArgs> callback = null;

mockEvent.Setup(
    p =>
    p.Subscribe(
        It.IsAny<Action<MyEventArgs>>(), 
        It.IsAny<ThreadOption>(), 
        It.IsAny<bool>(), 
        It.IsAny<Predicate<MyEventArgs>>()))
        .Callback<Action<MyEventArgs>, ThreadOption, bool, Predicate<MyEventArgs>>(
        (e, t, b, a) => callback = e);


// Do what you need to do to get it to subscribe

// Callback should now contain the callback to your event handler
// Which will allow you to invoke the callback on the test's thread
// instead of the UI thread
callback.Invoke(new MyEventArgs(someObject));

// Assert

Andere Tipps

Ich glaube wirklich, Sie spottet für alles und nicht die Eventaggregator verwenden sollten. Es ist nicht schwer, überhaupt zu verspotten ... Ich glaube nicht, die verknüpfte Antwort so gut wie nichts über die Testbarkeit des Eventaggregator beweist.

Hier ist Ihr Test. Ich benutze MSpec nicht, aber hier ist der Test in Moq. Sie lieferten keinen Code, so dass ich es auf dem Linked-zu-Code bin stützen. Ihr Szenario ist ein wenig härter als das verknüpfte Szenario, weil die andere OP wollte nur wissen, wie zu überprüfen, ob Abonnieren genannt wurde, aber Sie wirklich wollen, um die Methode nennen, die in der subscribe übergeben wurden ... etwas schwieriger, aber nicht sehr.

//Arrange!
Mock<IEventAggregator> eventAggregatorMock = new Mock<IEventAggregator>();
Mock<PlantTreeNodeSelectedEvent> eventBeingListenedTo = new Mock<PlantTreeNodeSelectedEvent>();

Action<int> theActionPassed = null;
//When the Subscribe method is called, we are taking the passed in value
//And saving it to the local variable theActionPassed so we can call it.
eventBeingListenedTo.Setup(theEvent => theEvent.Subscribe(It.IsAny<Action<int>>()))
                    .Callback<Action<int>>(action => theActionPassed = action);

eventAggregatorMock.Setup(e => e.GetEvent<PlantTreeNodeSelectedEvent>())
                   .Returns(eventBeingListenedTo.Object);

//Initialize the controller to be tested.
PlantTreeController controllerToTest = new PlantTreeController(eventAggregatorMock.Object);

//Act!
theActionPassed(3);

//Assert!
Assert.IsTrue(controllerToTest.MyValue == 3);

Sie können nicht so, wie es kann bedeuten, was Sie fühlen, ist ein „hässlicher Hack“, aber meine Präferenz ist ein echtes Eventaggregator zu verwenden, anstatt alles verspotten. Während angeblich eine externe Ressource, die Eventaggregator läuft im Speicher und erfordert so nicht viel Set-up, klar nach unten, und ist kein Flaschenhals wie andere externe Ressourcen wie Datenbanken, Web-Services, und so weiter sein würde und ich es deshalb fühlen ist geeignet in einer Einheitstest zu verwenden. Auf dieser Grundlage habe ich diese Methode zur Überwindung des UI-Thread Problems in NUnit mit minimaler Veränderung oder ein Risiko für meinen Produktionscode zum Zweck der Tests verwendet wird.

Zum einen habe ich eine Erweiterungsmethode wie folgt:

public static class ThreadingExtensions
{
    private static ThreadOption? _uiOverride;

    public static ThreadOption UiOverride
    {
        set { _uiOverride = value; }
    }

    public static ThreadOption MakeSafe(this ThreadOption option)
    {
        if (option == ThreadOption.UIThread && _uiOverride != null)
            return (ThreadOption) _uiOverride;

        return option;
    }

}

Dann in allen meinen Ereignisabonnements verwende ich die folgende:

EventAggregator.GetEvent<MyEvent>().Subscribe
(
    x => // do stuff, 
    ThreadOption.UiThread.MakeSafe()
);

In der Produktion Code, das funktioniert nur nahtlos. Für Prüfzwecke, alles, was ich tun muß, ist fügen Sie diese in meinem Set-up mit einem bisschen Synchronisationscode in meinem Test:

[TestFixture]
public class ExampleTest
{
    [SetUp]
    public void SetUp()
    {
        ThreadingExtensions.UiOverride = ThreadOption.Background;
    }

    [Test]
    public void EventTest()
    {
        // This doesn't actually test anything useful.  For a real test
        // use something like a view model which subscribes to the event
        // and perform your assertion on it after the event is published.
        string result = null;
        object locker = new object();
        EventAggregator aggregator = new EventAggregator();

        // For this example, MyEvent inherits from CompositePresentationEvent<string>
        MyEvent myEvent = aggregator.GetEvent<MyEvent>();

        // Subscribe to the event in the test to cause the monitor to pulse,
        // releasing the wait when the event actually is raised in the background
        // thread.
        aggregator.Subscribe
        (
            x => 
            {
                result = x;
                lock(locker) { Monitor.Pulse(locker); }
            },
            ThreadOption.UIThread.MakeSafe()
        );

        // Publish the event for testing
        myEvent.Publish("Testing");

        // Cause the monitor to wait for a pulse, but time-out after
        // 1000 millisconds.
        lock(locker) { Monitor.Wait(locker, 1000); }

        // Once pulsed (or timed-out) perform your assertions in the real world
        // your assertions would be against the object your are testing is
        // subscribed.
        Assert.That(result, Is.EqualTo("Testing"));
    }
}

Um die Warte zu machen und pulsierend prägnant Ich habe auch die folgenden Erweiterungsmethoden ThreadingExtensions hinzugefügt:

    public static void Wait(this object locker, int millisecondTimeout)
    {
        lock (locker)
        {
            Monitor.Wait(locker);
        }
    }

    public static void Pulse(this object locker)
    {
        lock (locker)
        {
            Monitor.Pulse(locker);
        }
    }

Dann kann ich tun:

// <snip>
aggregator.Subscribe(x => locker.Pulse(), ThreadOption.UIThread.MakeSafe());

myEvent.Publish("Testing");

locker.Wait(1000);
// </snip>

Auch wenn Sie Ihre Gefühle meinen Sie Mocks verwenden möchten, gehen für sie. Wenn Sie lieber die reale Sache verwenden würde, das funktioniert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top