chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
Hi,

I have a problem with the following code. The event is not invoked. What's wrong?

I'm using Isolator 6.0.2 with VS2008 and VS2010.

Regards
Stefan Lieser
--

using System.Net;
using async.tests.native.Version6;
using NUnit.Framework;
using TypeMock.ArrangeActAssert;

namespace async.tests.typemock
{
    [TestFixture, Isolated]
    public class ArtikelRepositoryTests
    {
        private ArtikelRepository sut;
        private WebClient webClient;

        [SetUp]
        public void Setup() {
            webClient = Isolate.Fake.Instance<WebClient>(Members.ReturnRecursiveFakes);
            Isolate.Swap.NextInstance<WebClient>().With(webClient);
            Isolate.WhenCalled(() => webClient.DownloadDataCompleted += null).CallOriginal();

            sut = new ArtikelRepository();
        }

        [Test, Isolated]
        public void Name() {
            var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();

            var count = 0;
            sut.OnArtikelGeladen += delegate { count++; };

            sut.LadeArtikel();
            Isolate.Invoke.Event(() => webClient.DownloadDataCompleted += null, null, e);

            Assert.That(count, Is.EqualTo(1));
        }
    }
}


using System;
using System.Collections.Generic;
using System.Net;
using async.tests.native.Version5;

namespace async.tests.native.Version6
{
    public class ArtikelRepository : IArtikelRepository
    {
        private readonly IArtikelReader artikelReader;

        public ArtikelRepository()
            : this(new ArtikelReader()) {
        }

        internal ArtikelRepository(IArtikelReader artikelReader) {
            this.artikelReader = artikelReader;
        }

        public void LadeArtikel() {
            var webClient = new WebClient();
            webClient.DownloadDataCompleted += OnCompleted;
            var url = new Uri("http://example.de/artikel");
            webClient.DownloadDataAsync(url);
        }

        public event Action<IEnumerable<Artikel>> OnArtikelGeladen = delegate { };

        private void OnCompleted(object sender, DownloadDataCompletedEventArgs e) {
            var artikel = artikelReader.Read(e.Result);
            OnArtikelGeladen(artikel);
        }
    }
}


using System.Collections.Generic;

namespace async.tests.native.Version5
{
    public class ArtikelReader : IArtikelReader
    {
        public IEnumerable<Artikel> Read(byte[] artikelXml) {
            yield break;
        }
    }
}
asked by slieser (1.2k points)

6 Answers

0 votes
The code seems right - I'll investigate this issue and let you know

Dror Helper
Typemock Support
answered by dhelper (11.9k points)
0 votes
I've investigated the code and there are and issue:

Although Isolator support setting behavior on events Swap.NextInstance does not swap the actual object only it's behavior so when you invoke the event you actually invoke an event on a different object.

Instead you can change the action to invoking the method that handles the event:
[SetUp]
public void Setup()
{
    Isolate.Swap.NextInstance<WebClient>().WithRecursiveFake();

    sut = new ArtikelRepository();
}

[Test]
public void Name()
{
    var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();

    var count = 0;
    sut.OnArtikelGeladen += delegate { count++; };

    sut.LadeArtikel();
    var eventArgs = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();
    Isolate.Invoke.Method(sut, "OnCompleted", new object[] {this, eventArgs});

    Assert.That(count, Is.EqualTo(1));
}
answered by dhelper (11.9k points)
0 votes
Thanks for looking into the issue!

Of course I'm not very happy with your suggestion.

1. The suggested fix doesn't need Isolator, except for instantiating the DownloadDataCompletedEventArgs which has an internal ctor.
2. Calling my private method by giving its name as a string isn't state of the art. I could make it internal and call it direct for example.
3. By calling my method the test will *not* test if the event handler is correctly attached.

So I'm not very happy with this "solution". Will TypeMock fix this issue?


Regards
Stefan Lieser
--
http://clean-code-developer.de
http://lieser-online.de
answered by slieser (1.2k points)
0 votes
The "issue" is actually an feature - the problem is that the WebClient inside the code under test is not the same as the one at the test becuase Swap.NextInstance swap only the behavior of the object.

You can pass the web client via the method call or c'tor instead of use Swap/NextInstance and then you'll be able to test the event invocation.


BTW - you still need to use Isolator to fake the WebClient :)
answered by dhelper (11.9k points)
0 votes
So to sum it up, it's not possible to raise the event without injecting a WebClient instance into the class under test?

I don't think that is a feature ;-) Not that I prefer classes that instanciate objects like WebClient by themself, but the #1 argument for TypeMock Isolator is that you can mock even such "bad designed" objects. So I would be very happy if you could show me a way to raise the DownloadDataCompleted event without injecting an instance of WebClient.


Regards
Stefan Lieser
--
http://clean-code-developer.de
http://lieser-online.de
answered by slieser (1.2k points)
0 votes
Hi Stefan,

I've found a workaround that should work for you:
[Test, Isolated]
public void Name()
{
    var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();

    var count = 0;
    sut.OnArtikelGeladen += delegate { count++; };

    sut.LadeArtikel();

    var internalWebClient = (WebClient) GetSwappedObject(webClient);

    Isolate.Invoke.Method(internalWebClient, "OnDownloadDataCompleted", e);

    Assert.That(count, Is.EqualTo(1));
}

private object GetSwappedObject(object instance)
{
    var mocks = MockManager.GetInstanceMocks(instance.GetType());

    object swappedObject = null;
    foreach (var mock in mocks)
    {
        if (ReferenceEquals(mock.MockedInstance, instance) == false)
        {
            swappedObject = mock.MockedInstance as WebClient;
        }
    }
    return swappedObject;
}


Keep in mind that this workaround would work if you have only one fake object and that you cannot call Invoke.Event on the swapped object (until we add the requested feature).
answered by dhelper (11.9k points)
...