おでんはじめました。

required ちくわぶ and 巾着,optional はんぺん.

DbSetのAddをMockでTestする

書きなれないブログでタイトルからつまづいてますが。。。
EF6をServiceとMockを使っていい感じでテスト&モッキューできないかなと。
元ネタはなかじさんのブログ。

blog.nakajix.jp

ここの記事にあるEFのTestをMoqを使ってやる記事があります。

msdn.microsoft.com

この中の「Testing query scenarios」が、サンプルデータをServiceを通してGetAllBlogsで取得するとorder byされて返ってくるというTESTです。

[TestMethod] 
public void GetAllBlogs_orders_by_name() 
{ 
    var data = new List<Blog> 
    { 
        new Blog { Name = "BBB" }, 
        new Blog { Name = "ZZZ" }, 
        new Blog { Name = "AAA" }, 
    }.AsQueryable(); 

    var mockSet = new Mock<DbSet<Blog>>(); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(0 => data.GetEnumerator()); 

    var mockContext = new Mock<BloggingContext>(); 
    mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 

    var service = new BlogService(mockContext.Object); 
    var blogs = service.GetAllBlogs(); 

    Assert.AreEqual(3, blogs.Count); 
    Assert.AreEqual("AAA", blogs[0].Name); 
    Assert.AreEqual("BBB", blogs[1].Name); 
    Assert.AreEqual("ZZZ", blogs[2].Name); 
} 

次にServiceでAddBlogすると追加されるTestをおこなうために下記を追加。
ここでなぜか(当然?)4件にならない、ということで本題に入ります。

service.AddBlog("CCC", "http://blogs.msdn.com/adonet");
var blogs = service.GetAllBlogs(); 
Assert.AreEqual(3, blogs.Count); //4件にならない

ServiceでAddBlogしたときに追加されるようにしたいわけですが、見つけたのが下記のAnswerの箇所。
要するに、DbSetでAddされる時のMockがないので追加されないとのこと。

stackoverflow.com

...Callbackがよくわからないのでちょっと勉強してから。

github.com

というわけで試行錯誤した結果一番最後の行ができたやつ。

//コピペのままではコンパイルされず
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback(blog => data.Add(blog));

//IQueryrableにAddはない
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback<Blog>(blog => data.Add(blog));   

//AddがないのでConcatで配列を作り直す→なぜかダメ
//mockSet.Setup(m => m.Add(It.IsAny<Blog>())).Callback((Blog blog) => data = data.Concat<Blog>(new[] { blog }))

//ListとIQueryableを上で使い分ける
mockSet.Setup(d => d.Add(It.IsAny<Blog>())).Callback<Blog>((s) => source.Add(s));

動いたソースを一応全部のっけておきます。

[Test]
public void GetAllBlogs_with_mock()
{
  var source = new List<Blog>
  {
    new Blog { Name = "BBB" },
    new Blog { Name = "ZZZ" },
    new Blog { Name = "AAA" },
  };
  var data = source.AsQueryable();
  
  var mockSet = new Mock<DbSet<Blog>>();
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
  mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

  //DbSetのAddをモックする
  //ListとIQueryableを上で使い分ける(これは仕方ない?ここをもうちょいきれいにしたい)
  mockSet.Setup(d => d.Add(It.IsAny<Blog>())).Callback<Blog>((s) => source.Add(s));
  
  var mockContext = new Mock<BloggingContext>();
  mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
  
  var service = new BlogService(mockContext.Object);
  var blogs = service.GetAllBlogs();
  
  Assert.AreEqual(3, blogs.Count,"追加前");
  Assert.AreEqual("AAA", blogs[0].Name);
  Assert.AreEqual("BBB", blogs[1].Name);
  Assert.AreEqual("ZZZ", blogs[2].Name);
  
  //もう一件追加する
  service.AddBlog("CCC", "http://blogs.msdn.com/adonet");
  blogs = service.GetAllBlogs();

  Assert.AreEqual(4, blogs.Count, "追加後");
  Assert.AreEqual("AAA", blogs[0].Name);
  Assert.AreEqual("BBB", blogs[1].Name);
  Assert.AreEqual("CCC", blogs[2].Name);
  Assert.AreEqual("ZZZ", blogs[3].Name);
}

で、下記に回答がありましたというオチです。

stackoverflow.com

追伸、 もっきゅーもっきゅーって居酒屋のメニューにしか聞こえないのは自分だけ?