Pergunta

Eu tenho uma aula, que assina um evento via agregador de eventos do PRISMS.

Como é um pouco difícil de zombar do agregador de eventos, conforme observado aqui, Apenas instanciei um real e o passo para o sistema em teste.

No meu teste, publico o evento através desse agregador e verifiquei como meu sistema em teste reage a ele. Como o evento será aumentado por um FileSystemwatcher durante a produção, quero usar o despacho automático, assinando o Uithread, para que eu possa atualizar minha interface do usuário quando o evento for criado.

O problema é que, durante o teste, o evento nunca é notado no sistema em teste, a menos que eu não se inscreva no Uithread.

Estou usando o MSPEC para meus testes, que eu corro de dentro do vs2008 via tdd.net. Adicionando [RequiresSta] para minha aula de teste não ajudou

Alguém tem uma solução, que me salva de mudar a opção Threads durante meus testes (por exemplo, por meio de uma propriedade - que hack que feio) ???

Foi útil?

Solução

Se você zombar do evento e do agregador de eventos e usar o retorno de chamada do MOQ, poderá fazê -lo.

Aqui está um exemplo:

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

Outras dicas

Eu realmente acho que você deveria usar zombarias para tudo e não para o EventAggregator. Não é difícil de zombar ... Não acho que a resposta vinculada prova muita coisa sobre a testabilidade do EventAggregator.

Aqui está o seu teste. Eu não uso o MSPEC, mas aqui está o teste no MOQ. Você não forneceu nenhum código, por isso estou baseando-o no código vinculado. Seu cenário é um pouco mais difícil do que o cenário vinculado, porque o outro OP só queria saber como verificar se a assinatura estava sendo chamada, mas você realmente quer chamar o método que foi aprovado na assinatura ... algo mais difícil, mas não muito.

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

Você pode não gostar disso, pois isso pode envolver o que você acha que é um "hack feio", mas minha preferência é usar um verdadeiro evento, em vez de zombar de tudo. Embora ostensivamente um recurso externo, o EventAGregreator é executado na memória e, portanto, não requer muita configuração, limpe e não é um pescoço de garrafa como outros recursos externos, como bancos de dados, serviços da web, etc. é apropriado para usar em um teste de unidade. Nessa base, usei esse método para superar o problema do encadeamento da interface do usuário no Nunit com alteração mínima ou risco para o meu código de produção em prol dos testes.

Em primeiro lugar, criei um método de extensão como assim:

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

}

Então, em todas as minhas assinaturas de eventos, uso o seguinte:

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

No código de produção, isso funciona perfeitamente. Para fins de teste, tudo o que preciso fazer é adicionar isso na minha configuração com um pouco de código de sincronização no meu teste:

[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 tornar a espera e a pulsação mais sucintas, também adicionei os seguintes métodos de extensão às 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);
        }
    }

Então eu posso fazer:

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

myEvent.Publish("Testing");

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

Novamente, se suas sensibilidades significam que você deseja usar zombarias, vá em frente. Se você preferir usar a coisa real, isso funciona.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top