Pregunta

Tengo una clase, que se suscribe a un evento a través de prismas agregador evento.

Como es algo difícil de burlar el agregador caso como se ha señalado aquí , acabo de crear una instancia de una de verdad y lo pasan al sistema bajo prueba.

En mi prueba que luego publicar el evento a través de ese agregador y después comprobar cómo mi sistema bajo prueba reacciona a ella. Dado que el evento se incrementará en un FileSystemWatcher durante la producción, quiero hacer uso de la expedición automática mediante la suscripción de la UIThread, por lo que puedo actualizar mi interfaz de usuario una vez que se ha producido el evento.

El problema es, que durante la prueba, el evento no se nota en el sistema bajo prueba a menos que no estoy suscrito en el UIThread.

Estoy utilizando MSpec para mis pruebas, que me encuentro desde el interior a través de VS2008 TDD.Net. Añadiendo [RequiresSta] a mi clase de prueba no ayuda

¿Alguien tiene una solución, que me salva de cambiar el ThreadOption durante mis pruebas (a través de una propiedad, por ejemplo - lo que es un truco feo) ???

¿Fue útil?

Solución

Si te burlas tanto el evento como el Agregador de eventos y de devolución de llamada uso de MOQ, usted puede hacerlo.

Este es un ejemplo:

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

Otros consejos

Realmente creo que debería utilizar burla de todo y no el EventAggregator. No es difícil burlarse de todo ... No creo que la respuesta ligado demuestra mucho de nada acerca de la capacidad de prueba de la EventAggregator.

Aquí está la prueba. No consumo MSpec, pero aquí está la prueba en Moq. No ha proporcionado ningún código, así que estoy basando en el código ligada a. Su escenario es un poco más difícil que el escenario relacionado porque el otro OP sólo quería saber cómo comprobar que Suscribirse estaba siendo llamado, pero en realidad se quiere llamar al método que se aprobó en el subscribe ... algo más difícil, pero no muy.

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

Es posible que no como éste, ya que puede implicar lo que sientes es un "truco feo", pero mi preferencia es usar un EventAggregator real en lugar de burlarse de todo. Aunque ostensiblemente un recurso externo, las carreras EventAggregator en la memoria y lo que no requiere mucho puesta a punto, abajo clara, y no es un cuello de botella como otros recursos externos, tales como bases de datos, servicios web, etcétera serían y por lo tanto creo que es apropiado para utilizar en una prueba de unidad. Sobre esa base he utilizado este método para superar el problema hilo de interfaz de usuario en NUnit con un cambio mínimo o un riesgo para mi código de producción por el bien de las pruebas.

En primer lugar me creado un método de extensión de este modo:

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

}

A continuación, en todas mis suscripciones de eventos utilizo el siguiente:

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

En el código de producción, esto sólo funciona a la perfección. Para propósitos de prueba, todo lo que tengo que hacer es poner esto en mi puesta a punto con un poco de código de sincronización en mi prueba:

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

Para hacer la espera y pulsante más sucinta También he añadido los siguientes métodos de extensión a 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);
        }
    }

Entonces puedo hacer:

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

myEvent.Publish("Testing");

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

Una vez más, si su sensibilidad media desee utilizar burla, ir a por ello. Si prefieres usar la cosa real, esto funciona.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top