かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WCF RIA ServicesのDomainContextをモックする方法

WCF RIA Serviceは非常に便利なんですが、ViewModelからDomainContextを使うと、途端に単体テストがしづらいという悲しいことが起きてしまいます。


ということで、DomainContextをラップするクラスを作って、そいつをモックにしてやるという方法がありますが、あまりWCF RIA Serviceで得られる開発効率がいい!という恩恵をなんだか捨ててるような気がします。

ということで、妥協案?としてDomainContextのコンストラクタにDomainClientというクラスを渡すことで、DomainContextがデータをとってくる先をすげ替えることができるという作りになっているのを利用してDomainContextがデータをとってくる先を適当なところに置き換える方法を紹介したいと思います。


とりあえず、題材とするDomainServiceは、以下ようなPersonクラスを返すだけのシンプルなものにします。

namespace WCFRIAMock.Web
{
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;

    [EnableClientAccess()]
    public class PeopleDomainService : DomainService
    {
        public IQueryable<Person> GetPeople()
        {
            // サーバーサイドは何も返さない
            return null;
        }
    }

    public class Person
    {
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
    }
}


こいつをビルドするとSilverlightプロジェクト側にPeopleDomainContextクラスとPersonクラスが作られます。この例の場合は、データを何も返さないのでDataGridとかにバインドしても大したことにはなりません。

さて、こいつをモックからデータをとってくるようにします。DomainClientクラスを実装すると、以下のようなメソッドを実装するように要求されます。

namespace WCFRIAMock
{
    using System;
    using System.ServiceModel.DomainServices.Client;

    public class MockDomainClient : DomainClient
    {

        protected override IAsyncResult BeginQueryCore(EntityQuery query, AsyncCallback callback, object userState)
        {
            throw new NotImplementedException();
        }

        protected override InvokeCompletedResult EndInvokeCore(IAsyncResult asyncResult)
        {
            throw new NotImplementedException();
        }

        protected override QueryCompletedResult EndQueryCore(IAsyncResult asyncResult)
        {
            throw new NotImplementedException();
        }

        protected override SubmitCompletedResult EndSubmitCore(IAsyncResult asyncResult)
        {
            throw new NotImplementedException();
        }
    }
}

とりあえず、いろいろメソッドがありますが、今回はデータをとってくる部分をモックにしたいのでBeginQueryCoreとEndQueryCoreを実装します。まず、IAsyncResultのなんちゃって実装をでっちあげます。
そして、そのなんちゃってIAsyncReusltを使ってBeginQueryCoreとEndQueryCoreを実装します。

namespace WCFRIAMock
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.ServiceModel.DomainServices.Client;
    using System.Threading;
    using WCFRIAMock.Web;

    public class MockDomainClient : DomainClient
    {

        protected override IAsyncResult BeginQueryCore(EntityQuery query, AsyncCallback callback, object userState)
        {
            var mockAsyncResult = new MockAsyncResult(
                new[] { new Person { ID = 1, Name = "もっく 太郎" } }.Cast<Entity>(),
                callback,
                userState);
            SynchronizationContext.Current.Post(o =>
            {
                mockAsyncResult.Complete();
            }, null);

            return mockAsyncResult;
        }

        protected override InvokeCompletedResult EndInvokeCore(IAsyncResult asyncResult)
        {
            throw new NotImplementedException();
        }

        protected override QueryCompletedResult EndQueryCore(IAsyncResult asyncResult)
        {
            var mockAsyncResult = asyncResult as MockAsyncResult;
            return new QueryCompletedResult(
                mockAsyncResult.Entities,
                Enumerable.Empty<Entity>(),
                0,
                Enumerable.Empty<ValidationResult>());
        }

        protected override SubmitCompletedResult EndSubmitCore(IAsyncResult asyncResult)
        {
            throw new NotImplementedException();
        }
    }

    public class MockAsyncResult : IAsyncResult
    {
        private AsyncCallback callback;

        public MockAsyncResult(IEnumerable<Entity> entities, AsyncCallback callback, object asyncState)
        {
            this.Entities = entities;
            this.callback = callback;
            this.AsyncState = asyncState;
        }

        public void Complete()
        {
            this.IsCompleted = true;
            this.callback(this);
        }

        public IEnumerable<Entity> Entities
        {
            get;
            private set;
        }

        public object AsyncState
        {
            get;
            private set;
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return null; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get;
            private set;
        }
    }

}

このMockDomainClientをDomainContextのコンストラクタで渡してやるようにすると・・・

namespace WCFRIAMock
{
    using System.Windows.Controls;
    using WCFRIAMock.Web;

    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            // DomainClientをMockに差し替える
            this.personDomainDataSource.DomainContext = new PeopleDomainContext(
                new MockDomainClient());
        }

        private void personDomainDataSource_LoadedData(object sender, System.Windows.Controls.LoadedDataEventArgs e)
        {

            if (e.HasError)
            {
                System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
                e.MarkErrorAsHandled();
            }
        }
    }
}

実行すると、ダミーデータとして仕込んだデータが取得されているのがわかります。

応用すると、DomainContextを使うSilverlightのViewModelの単体テストがやりやすくするライブラリとか作れるかもしれません。