13

I have a UserControl which publishes an EventAggregator message in its Loaded event. In order to test this (and get the Loaded event raised) I am currently creating a window and adding the control to it, then waiting for the Loaded event to be raised.

Is there any way to setup a test so that the Loaded event fires without having to create and add the control to a window?

For example:

[Test, RequiresSTA]
public void active_thingy_message_is_published_on_loaded()
{
    const string TestMsg = "Active thingy changed";

    using (AutoResetEvent loadedEvent = new AutoResetEvent(false))
    {
        DummyEventService eventService = new DummyEventService();                
        DummyControl control = new DummyControl(eventService, TestMsg);
        control.Loaded += delegate { loadedEvent.Set(); };

        Assert.That(eventService.Message, Is.Null, "Before.");
        Window window = new Window { Content = control };
        window.Show();                
        loadedEvent.WaitOne();
        window.Dispatcher.InvokeShutdown();
        Assert.That(eventService.Message, Is.EqualTo(TestMsg), "After.");
    }
}

private class DummyControl : UserControl
{
    public DummyControl(DummyEventService eventService, string testMsg)
    {
        Loaded += delegate { eventService.Publish(testMsg); };
    }
}

private class DummyEventService
{
    public string Message { get; private set; }
    public void Publish(string msg) { Message = msg; }
}

Update

I've changed the title from "Unit Testing..." to "Testing...", and replaced the tag "unit-testing" with "testing".

I would prefer not to split hairs over exactly what class of test this is, as it is not constructive. Yes it could be argued that this is not a "Unit Test", but that's not helpful. I want to test an issue that is dependent on the control's life-cycle and this involves the Loaded event. It's an important regression test, as 3rd party components I have no control over depend on the message being raised at Loaded.

Can the Loaded event be raised without adding the control to a window?

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
  • You will have to fake/mock the Window... But this doesn't look like unit-testing anymore. – H H Aug 19 '11 at 23:05
  • @Henk How do you see a faked window causing my user control to be loaded, and hence fire its `Loaded` event? – Tim Lloyd Aug 19 '11 at 23:06
  • You could try raising the Load event via reflection: http://stackoverflow.com/questions/198543/how-do-i-raise-an-event-via-reflection-in-net-c – Mike Zboray Aug 19 '11 at 23:38
  • @mike I suspect that WPF events are not implemented in the same way. I cannot see an event backing field via reflection using the approach outlines in your link. Already tried that approach, but will dig further. Thanks. – Tim Lloyd Aug 19 '11 at 23:52
  • @mike Had a dig around, WPF events are definitely not implemented in the same way as "traditional" events. – Tim Lloyd Aug 20 '11 at 00:08
  • I see. I thought it might be possible, but it appears that when you have a lot of events like a WPF control the standard backing not efficient. What's the problem with the above code? I guess it's not a proper unit test but it does seem like it should be pretty stable and deterministic. – Mike Zboray Aug 20 '11 at 00:41
  • @mike Yeah, WPF uses a mechanism similar to dependency properties to manage event subscriptions, presumably for similar reasons e.g. memory usage. Even so, it should still be possible to use reflection to tease it out, just a lot more tortuous than traditional events with a backing field. Why am I not keen on using a window? It just feels very odd in a unit test, a window pops up (although I can lessen this with window style and size, etc) and it takes quite a long time to run for a unit test. – Tim Lloyd Aug 20 '11 at 08:18
  • Your unit test seems strange because your asserts should match what you're trying to test. You're trying to test an event, and are verifying a state instead. Mock object frameworks can make it easier to verify a behavior/interaction without verifying state. Also, if you use something like MVC or MVVM, your UI layer might be thin enough that you could get away without testing it. If not, then it makes sense to pop up windows, and even to interact with it. But I'd push those tests to lower priority, as they wouldn't verify core business logic. – Merlyn Morgan-Graham Aug 20 '11 at 08:59
  • @Merlyn Sorry, but the unit test is just demonstrating waiting for a `Loaded` event, it's not a real unit test. My actual environment really does rely on the `Loaded` event and publishing a message, which must be sent. The unit test therefore tests that this happens and then also acts as a regression test. I will update the unit test code to make it look more obvious. – Tim Lloyd Aug 20 '11 at 09:17
  • 1
    @Merlyn I have updated the sample unit test. – Tim Lloyd Aug 20 '11 at 09:18
  • @chibacity: The sample looks much better. I don't know the scope of your app, but if you have UI test and non-UI tests, you could categorize them so they're not all run at once, and just run these during larger-width test passes. See: http://stackoverflow.com/questions/6737387#6737440. UI unit tests make perfect sense. Your alternative is automated or manual full-app integration tests (and writing/maintaining those types of tests is not fun). – Merlyn Morgan-Graham Aug 20 '11 at 20:04
  • @Merlyn I have some very specific cases that I want to test around the Loaded event. I'm really looking for a specific way of causing the Loaded event to be raised without adding the control to a window. Shutting down the dispatcher is also causing issues, but that's a different question. – Tim Lloyd Aug 22 '11 at 19:19
  • Have you tried [this](http://msdn.microsoft.com/en-us/library/ms788731.aspx)? – dowhilefor Aug 22 '11 at 19:27
  • @dowhilefor Thanks for the suggestion, but that technique does not cause `Loaded` to be called. – Tim Lloyd Aug 22 '11 at 21:41

4 Answers4

8

In the past two years, perhaps things have changed. For the sake of code coverage, I ran into this problem as well, and it's solution.

WPF UIElements inherit a method called RaiseEvent, which takes a RoutedEventArgs. This can be constructed using the particular UIElement's .LoadedEvent, allowing you to get that final bit of code coverage.

I doubt you still need my answer, but someone may.

Magus
  • 1,302
  • 9
  • 16
8

If you are just interested in firing the Loaded event of the target control, then Reflection should do the trick.

public static void RaiseLoadedEvent(FrameworkElement element)
{
    MethodInfo eventMethod = typeof(FrameworkElement).GetMethod("OnLoaded",
        BindingFlags.Instance | BindingFlags.NonPublic);

    RoutedEventArgs args = new RoutedEventArgs(FrameworkElement.LoadedEvent);

    eventMethod.Invoke(element, new object[] { args });
}

This literally fires the OnLoaded method that is present in each FrameworkElement, so if your test requires Application state, this won't work.

Also, there is no relationship between the Loaded event of a parent and it's children. If a test requires the child elements to fire their Loaded events, then the helper method will need to manually walk the child controls and fire those as well.

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
Paul Walls
  • 5,884
  • 2
  • 22
  • 23
5

Refactor what's in the Loaded event handler into its own method, and have the Loaded event handler call it. Write your unit test to test the refactored method, not the Loaded event.

Unit testing is verifying that a unit does what it's supposed to do. Testing how the unit interoperates with other units is integration testing. It's certainly valuable to do integration testing, and to be able to regression test integrated units, but that's a different task from unit testing.

Finally: if you don't have confidence that the control's Loaded event is being fired when it loads (which is the primary reason that you'd do integration tests like this), something's very wrong and you should investigate it.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • +1 for the "if you don't have confidence" comment. Sometimes it is best not to dogmatically adhere to a methodology if it doesn't buy you anything. In this case, a code review is probably plenty sufficient to verify the event binding. – Merlyn Morgan-Graham Aug 22 '11 at 19:40
  • And if it's not, there's a problem somewhere that needs to be fixed. – Robert Rossney Aug 22 '11 at 20:20
  • @Robert @Merlyn I have removed "Unit" from the title and tags, so that it's clear I want to specifically test the issue without getting bogged down in what class of test it is. I'm not being dogmatic at all. The message being published in the `Loaded` event is very important in terms of life-cycle, and how messaging plays with other 3rd party components in my system which I have *no* control over. Although it's tempting to challenge and dissect what class of test this is, it does not solve my problem which is a valid and important test case. – Tim Lloyd Aug 22 '11 at 21:44
  • @Robert I have every confidence that the `Loaded` event will be fired - the WPF framework does this! What I am not confident about is that refactoring, code changes, etc will break this code - I want a regression test. If this code is broken, it is difficult for someone else to diagnose the issue. A failing test is an excellent solution to this. – Tim Lloyd Aug 22 '11 at 21:53
  • Fair enough. I think that probably the most effective place to do this kind of testing is by building a WPF application that exercises the components together as thoroughly as you expect them to be used in the wild. It's not as convenient as having a script that a robot can run. But it also genuinely tests everything. The more mocking you do to get this up in NUnit or whatever, the greater the risk that you've mocked something improperly and your tests are hiding problems, not finding them. – Robert Rossney Aug 22 '11 at 22:05
  • @Robert I think the point's being missing. I already have a method of asserting my test case which I give a pseudo example of in the question - it does the job adequately. However, I would like to know if it's possible to carry out the test without using a window. I even know a dirty solution, but I am hoping that someone on SO can do it better. – Tim Lloyd Aug 22 '11 at 22:09
  • 2
    I think I understood your point. I believe the answer is no. More precisely, I believe that while you may ultimately find a way of creating an instance of this control outside the context of a WPF application and getting its `Loaded` event to fire, what you will have done to make this possible will undermine the reliability of the test. – Robert Rossney Aug 22 '11 at 22:35
  • Have to agree with Robert here. The only thing you'd get by actually causing the `Loaded` event to fire would be testing the tiny bit of glue code that's calling into the refactored method. This is best left up to end-to-end testing I think; you don't need to try and force it into an isolated test. – RandomEngy Aug 25 '11 at 17:25
  • @RandomEngy I already have a working isolated test - that is not the question here - no forcing has been involved.It may seem to you that it's only testing a small bit of glue code, but it's susceptible to being broken via maintenance and refactoring, and if it's broken it's disproportionately difficult to diagnose as it breaks loosely coupled messaging within a complex application. The test provides *good* value and is automated. If I could publish the message in the control constructor, great, but I can't. I only want to know if I can do it without using a window. It's a technical question. – Tim Lloyd Aug 27 '11 at 11:09
0

Popup does the trick for me:

public static void LoadControl(Control cnt)
{
    var popup = new Popup();
    popup.Child = cnt;
    popup.IsOpen = true;
}

It looks much better than Window as doesn't show anything on the screen. Should be safe to use on servers. (I haven't tried it in .Net Framework, only in .Net 6)

notacat
  • 671
  • 6
  • 11