#if UNITTEST using System; using System.ComponentModel; using System.Threading; using NUnit.Framework; namespace Nuclex.Windows.Forms.ViewModels { /// Unit test for the threaded view model base class [TestFixture] public class ThreadedViewModelTest { #region class DummyContext /// Synchronization context that does absolutely nothing private class DummyContext : ISynchronizeInvoke { #region class SimpleAsyncResult /// Barebones implementation of an asynchronous result private class SimpleAsyncResult : IAsyncResult { /// Ehether the asynchronous operation is complete /// /// Always true because it completes synchronously /// public bool IsCompleted { get { return true; } } /// /// Wait handle that can be used to wait for the asynchronous operation /// public WaitHandle AsyncWaitHandle { get { throw new NotImplementedException("Not implemented"); } } /// Custom state that can be used to pass information around public object AsyncState { get { throw new NotImplementedException("Not implemented"); } } /// Whether the asynchronous operation completed synchronously public bool CompletedSynchronously { get { return true; } } /// The value returned from the asynchronous operation public object ReturnedValue; } #endregion // class SimpleAsyncResult /// Whether the calling thread needs to use Invoke() public bool InvokeRequired { get { return true; } } /// Schedules the specified method for execution in the target thread /// Method the target thread will execute when it is idle /// Arguments that will be passed to the method /// /// An asynchronous result handle that can be used to check on the status of /// the call and wait for its completion /// public IAsyncResult BeginInvoke(Delegate method, object[] arguments) { var asyncResult = new SimpleAsyncResult(); asyncResult.ReturnedValue = method.Method.Invoke(method.Target, arguments); return asyncResult; } /// Waits for the asychronous call to complete /// /// Asynchronous result handle returned by the method /// /// The original result returned by the asychronously called method public object EndInvoke(IAsyncResult result) { return ((SimpleAsyncResult)result).ReturnedValue; } /// /// Schedules the specified method for execution in the target thread and waits /// for it to complete /// /// Method that will be executed by the target thread /// Arguments that will be passed to the method /// The result returned by the specified method public object Invoke(Delegate method, object[] arguments) { return method.Method.Invoke(method.Target, arguments); } } #endregion // class DummyContext #region class TestViewModel /// View model used to unit test the threaded view model base class private class TestViewModel : ThreadedViewModel { /// /// Initializes a new view model, letting the base class figure out the UI thread /// public TestViewModel() : base() { this.finishedGate = new ManualResetEvent(initialState: false); } /// /// Initializes a new view model, using the specified context for the UI thread /// /// Synchronization context of the UI thread public TestViewModel(ISynchronizeInvoke uiContext) : base(uiContext) { this.finishedGate = new ManualResetEvent(initialState: false); } /// Immediately releases all resources owned by the instance public override void Dispose() { base.Dispose(); if(this.finishedGate != null) { this.finishedGate.Dispose(); this.finishedGate = null; } } /// Waits until the first background operation is finished /// /// True if the background operation is finished, false if it is ongoing /// public bool WaitUntilFinished() { return this.finishedGate.WaitOne(100); } /// Runs a background process that causes the specified error /// Error that will be caused in the background process public void CauseErrorInBackgroundThread(Exception error) { RunInBackground(() => throw error); } /// Last error that was reported by the threaded view model public Exception ReportedError { get { return this.reportedError; } } /// Called when an error occurs in the background thread /// Exception that was thrown in the background thread protected override void ReportError(Exception exception) { this.reportedError = exception; this.finishedGate.Set(); } /// Last error that was reported by the threaded view model private Exception reportedError; /// Triggered when the private ManualResetEvent finishedGate; } #endregion // class TestViewModel /// Verifies that the threaded view model has a default constructor [Test] public void HasDefaultConstructor() { using(var mainForm = new System.Windows.Forms.Form()) { mainForm.Show(); try { mainForm.Visible = false; using(new TestViewModel()) { } } finally { mainForm.Close(); } } } /// /// Verifies that the threaded view model can be constructed with a custom UI context /// [Test] public void HasCustomSychronizationContextConstructor() { using(new TestViewModel(new DummyContext())) { } } /// Checks that a new view model starts out idle and not busy [Test] public void NewInstanceIsNotBusy() { using(var viewModel = new TestViewModel(new DummyContext())) { Assert.IsFalse(viewModel.IsBusy); } } /// /// Verifies that errors happening in the background processing threads are /// reported to the main thread /// [Test] public void ErrorsInBackgroundThreadAreReported() { using(var viewModel = new TestViewModel(new DummyContext())) { var testError = new ArgumentException("Mooh"); viewModel.CauseErrorInBackgroundThread(testError); viewModel.WaitUntilFinished(); Assert.AreSame(testError, viewModel.ReportedError); } } } } // namespace Nuclex.Windows.Forms.ViewModels #endif // UNITTEST