This project has moved and is read-only. For the latest updates, please go here.

Unit/integration testing Legacy Windows Forms application not optimised for unit testing

Oct 6, 2015 at 5:40 PM
Edited Oct 6, 2015 at 6:37 PM
Hi,
I have a windows forms application with a splash/login screen.
When my unit tests start, I want to run through the splash/login code. Accordingly I've refactored that code to use a viewmodel which contains as much non-ui code as possible.

The startup/login process involves a cpu intensive operation, as well as web service calls, all whilst remaining responsive to the user, and providing feedback on the startup process. (I'm using Task.TaskFactory.StartNew and ContinueWith - both with TaskScheduler.Default)

My challenge is running the start-up code in a unit test scenario.
Ideally I want to have a thread that mocks the UI thread.

As well as making viewmodel classes, I also have used UI delegates (test and normal runtime versions) where some classes (otherwise pure code) pop up some ui control.

Are there any patterns/best practices for unit testing legacy windows forms applications? I'm hopeful that one of the utility classes in Nito Asynchronous Library can help me. I've also just bought a copy of Concurrency in C# Cookbook for reference. Rx Extensions look interesting

I am currently getting ThreadAbortException when running unit tests, so I'm wondering if my threads are not equivalent between unit test world and normal runtime world. In particular how events raised through the ui are absent in my unit tests. - I currently have a signal that I wait on with a timeout period - allowing for a set period of time for the startup process to complete. - I send a signal on the other thread when the login completes.

Kind Regards
Martin
Ps I'm restricted to .net 4.0
Oct 6, 2015 at 8:23 PM
It depends on how your code is attempting to synchronize to the main thread.

By default, unit test methods just run on the thread pool; there's no UI thread. If your code uses non-Winforms-specific ways of syncing to the UI thread (i.e., SynchronizationContext), then you can try using either the AsyncContext type from my newer AsyncEx library, or the ActionDispatcher from this older library. If the code uses Winforms-specific ways of syncing to the UI thread (i.e., Control.Invoke), then you can use a WindowsFormsContext type written by Stephen Toub.
Oct 6, 2015 at 11:22 PM
Thanks for your response Stephen,
The code currently uses Control.Invoke
I'll try your suggestion tomorrow. (11pm now)
Martin
Oct 8, 2015 at 12:39 PM
Edited Oct 8, 2015 at 2:06 PM
Hi Stephen,
I've made my legacy code use a SynchronizationContext (I call it's Post method when I want to talk to the UI)
This works fine at normal runtime. (NB limited testing so far)

I was expecting to be able to use ActionDispatcher within my mocked ui delegate class, and be able to get its SynchronizationContext via the same SynchronizationContext.Current that gives me the genuine forms' ui context.
However in unit test mode, the context is null. - I need to it to give me an instance of ActionDispatcherSynchronizationContext, on which I can call the Post method as at normal runtime.

Before looking for the context, I start a Task on the TaskScheduler.Default to call ActionDispatcher's Run method

Seems like I'm misunderstanding something...

[Edit]
ok - After creating the ActionDispatcher, I also do this:
            SyncContext = new ActionDispatcherSynchronizationContext(Dispatcher);
            SynchronizationContext.SetSynchronizationContext(SyncContext);
I now get the SynchronizationContext as expected and can do posts.
Just need to deal with the QueueExit action now

Thanks
Martin
Oct 9, 2015 at 7:17 PM
The ActionDispatcher should be setting SynchronizationContext.Current only for code that runs in the ActionDispatcher. This is by design; it shouldn't be setting SynchronizationContext.Current for the thread running the unit test.

As long as you create your type from within the ActionDispatcher, it should work fine.
Oct 9, 2015 at 9:04 PM
Edited Oct 9, 2015 at 9:05 PM
Hmm. I might have it wrong then.

I'm expecting to be able to use the SychronizationContext for two purposes:
1) Queueing of actions (via delegates)
2) To run on a specific thread (the one that Dispatcher is running on)

The code I have at the moment does this:
            Dispatcher = new ActionDispatcher();
            SyncContext = new ActionDispatcherSynchronizationContext(Dispatcher);
            SynchronizationContext.SetSynchronizationContext(SyncContext);
             ......
            dispatcherTask = new Task(() => { Dispatcher.Run(); });  //equivalent to Application.Run in Main()
            dispatcherTask.Start(TaskScheduler.Default);
            ........
            ViewModel = new UserLoginVM(SyncContext); // let viewmodel reference the syncContext to call Send and / or Post as required in various event handlers
            
I think what you're saying is that the code should be like this:?

             ......
            dispatcherTask = new Task(() => 
            {
                 Dispatcher = new ActionDispatcher();
                 SyncContext = new ActionDispatcherSynchronizationContext(Dispatcher);
                 SynchronizationContext.SetSynchronizationContext(SyncContext);
 
                 ViewModel = new UserLoginVM(SyncContext); // let viewmodel reference the syncContext to call Send and / or Post as required in various event handlers

                 Dispatcher.Run(); 
            });  //equivalent to Application.Run in Main()
            dispatcherTask.Start(TaskScheduler.Default);
       
             
Can you clarify what you mean by "within the ActionDispatcher"

Is there an example you can point me at?

Thanks again for your help
Oct 9, 2015 at 11:31 PM
I mean that you shouldn't have to call SetSynchronizationContext at all.

You can use a thread pool task (i.e., Task.Run, please don't use the Task constructor), but it would be easier to use ActionThread.

Something like this:
var thread = new ActionThread();
thread.Start();
thread.Do(() => {
  var viewModel = new UserLoginVM(SynchronizationContext.Current);
  ...
});

// Whenever you're ready to be done
thread.Join();
Note that you have to manage the "ready to be done" signal yourself, e.g., a ManualResetEvent.

On a side note, this library is considerably old (>6 years) and I haven't used it in a looong time. There's a more modern equivalent of ActionThread called AsyncContextThread in my newer AsyncEx library that is a bit easier to use.
Oct 13, 2015 at 10:56 AM
Hi Stephen,

I haven't had a chance to use your new advice yet, but I'd just like to thank you for your help.

Martin