Comment puis-je tester événement Prism abonnements agrégateurs, sur l'interface utilisateur de cette discussion?

StackOverflow https://stackoverflow.com/questions/2375178

Question

I ai une classe, qui souscrit à un événement de l'événement par l'intermédiaire d'agrégateur prismes.

Comme il est un peu difficile de se moquer de l'aggrégateur événement comme indiqué ici , je viens instancier un vrai et de le transmettre au système en cours de test.

Dans mon test, je puis publie l'événement via ce aggrégateur et vérifier comment mon système testé réagit. Depuis l'événement sera soulevée par un FileSystemWatcher pendant la production, je veux utiliser l'envoi automatique en vous inscrivant sur le UIThread, donc je peux mettre à jour mon interface utilisateur une fois que l'événement est déclenché.

Le problème est que lors de l'essai, l'événement ne se fait remarquer dans le système en cours de test à moins que je ne souscris pas à la UIThread.

J'utilise MSpec pour mes tests, que je dirige à l'intérieur VS2008 via TDD.Net. Ajout [RequiresSta] à ma classe de test n'a pas aidé

Quelqu'un at-il une solution, qui me sauve de changer le ThreadOption pendant mes tests (par exemple via une propriété - quelle bidouille horrible) ???

Était-ce utile?

La solution

Si vous vous moquez de l'événement et l'événement Aggregator, et utiliser le rappel automatique de moq, vous pouvez le faire.

Voici un exemple:

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

Autres conseils

Je pense vraiment que vous devriez utiliser se moque de tout et non la EventAggregator. Il est difficile de ne pas se moquer du tout ... Je ne pense pas que la réponse liée prouve quoi que ce soit au sujet de la testabilité du EventAggregator.

Voici votre test. Je ne me MSpec, mais voici le test en Moq. Vous n'avez pas fourni de code, donc je me base sur le code lié à. Votre scénario est un peu plus difficile que le scénario lié parce que l'autre OP voulait juste savoir comment vérifier que Abonnez-vous a été appelé, mais vous voulez vraiment appeler la méthode qui a été adoptée dans la ... abonnez-vous quelque chose de plus difficile, mais pas très.

//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);

Vous ne pouvez pas aimer ce qu'il peut impliquer ce que vous ressentez est un « hack laid », mais ma préférence est d'utiliser un vrai EventAggregator plutôt que tout se moquant. Alors que ostensiblement une ressource externe, le EventAggregator fonctionne en mémoire et ne requiert donc pas beaucoup de set-up, clair vers le bas, et n'est pas un goulot de bouteille comme d'autres ressources externes telles que les bases de données, de services Web, etcetera seraient donc je le sens est appropriée à utiliser dans un test unitaire. Sur cette base, je l'ai utilisé cette méthode pour surmonter le problème de thread d'interface utilisateur dans NUnit avec un changement minimal ou un risque pour mon code de production pour le bien des tests.

Tout d'abord, je créé une méthode d'extension comme ceci:

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

}

Alors, dans tous mes abonnements d'événements que j'utilise les éléments suivants:

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

Dans le code de production, cela fonctionne très transparente. Pour fins de test, tout ce que je dois faire est d'ajouter dans mon set-up avec un peu de code de synchronisation dans mon 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"));
    }
}

Pour l'attente et pulsant plus succinct que j'ai également ajouté les méthodes d'extension suivantes à ThreadingExtensions:

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

Alors je peux faire:

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

myEvent.Publish("Testing");

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

Encore une fois, si votre sensibilité que vous voulez dire veulent utiliser simulacres, allez-y. Si vous préférez utiliser la vraie chose, cela fonctionne.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top