模拟DbContext SaveChanges()方法不返回任何值

时间:2016-09-08 04:00:59

标签: c# unit-testing entity-framework-6 moq

我想知道是否有人知道如何使用Moq模拟SaveChanges()DbContext返回0以外的值。我经常在代码中使用SaveChanges(),如果项目数更改已保存或未保存小于或大于预期的更改次数,则会抛出错误。

根据我使用Moq的经验,如果您没有调用数据库,DbContext.SaveChanges()看起来就不会做任何事情。

2016年9月8日更新

Jeroen Heier和Nkosi的建议有助于提供解决方案。我应该深入挖掘Mock并意识到SaveChanges()是虚拟的(呃!)。作为参考,我将添加下面的代码来演示我正在做的事情。需要注意的一点是,如果业务逻辑取决于保存到数据库 DbContext.Setup().Callback(() => { /* do something */}) 的项目数,则需要定义回调以保存更改。使用回调,您可以跟踪调用保存更改的预期次数,并将其置于测试中。

PostService类中的

“SavePost”方法

public PostViewModel SavePost(PostViewModel currentPost, bool publishPost)
{
    //_context.Configuration.ProxyCreationEnabled = false;

    JustBlogContext currentContext = (JustBlogContext)_Context;
    Post newPost = new Post();
    List<Tag> postTags = new List<Tag>();
    DateTime currentDateTime = DateTime.UtcNow;
    bool saveSuccess = false;

    if (currentPost != null)
    {
        //Post
        newPost = new Post()
        {
            Category = currentPost.Category.Id.Value,
            Description = currentPost.Description,
            ShortDescription = currentPost.ShortDescription,
            Title = currentPost.Title,
            UrlSlug = currentPost.UrlSlug,
            Published = currentPost.Published == true || publishPost == true ? true : false,
            Meta = currentPost.Meta == "" || currentPost.Meta == null ? "No meta data" : currentPost.Meta,
            PostedOn = currentPost.PostedOn,
            Modified = currentDateTime
        };

        //Tags
        foreach (Tag currentTag in currentPost?.Tags)
        {
            postTags.Add(new Tag()
            {
                Description = currentTag.Description,
                Id = currentTag.Id,
                Name = currentTag.Name,
                UrlSlug = currentTag.UrlSlug
            });
        }

        if (currentPost.PostedOn == null && publishPost)
        {
            newPost.PostedOn = currentDateTime;
        }

        /**
         * Note that you must track all entities
         * from the Post side of the Post - PostTagMap - Tag database schema.
         * If you incorrectly track entites you will add new tags as opposed to
         * maintaining the many-to-many relationship.
         **/
        if (currentPost?.Id == null)
        {
            //Add a new post
            //Attach tags from the database to post
            foreach (Tag clientTag in postTags)
            {
                if (currentContext.Entry(clientTag).State == EntityState.Detached)
                {
                    currentContext.Tags.Attach(clientTag);
                }
            }

            newPost.Tags = postTags;
            currentContext.Posts.Add(newPost);
            saveSuccess = currentContext.SaveChanges() > 0 ? true : false;
        }
        else
        {
            //Modify and existing post.
            bool tagsModified = false;
            newPost.Id = currentPost.Id.Value;
            currentContext.Posts.Attach(newPost);
            currentContext.Entry(newPost).State = EntityState.Modified;

            saveSuccess = currentContext.SaveChanges() > 0 ? true : false;
            List<Tag> dataTags = currentContext.Posts.Include(post => post.Tags).FirstOrDefault(p => p.Id == newPost.Id).Tags.ToList();

            //Remove old tags
            foreach (Tag tag in dataTags)
            {
                if (!postTags.Select(p => p.Id).ToList().Contains(tag.Id))
                {
                    tagsModified = true;
                    newPost.Tags.Remove(tag);
                }
            }

            if (postTags.Count() > 0)
            {
                //Add new tags
                foreach (Tag clientTag in postTags)
                {
                    //Attach each tag because it comes from the client, not the database
                    if (!dataTags.Select(p => p.Id).ToList().Contains(clientTag.Id))
                    {
                        currentContext.Tags.Attach(clientTag);
                    }

                    if (!dataTags.Select(p => p.Id).ToList().Contains(clientTag.Id))
                    {
                        tagsModified = true;
                        newPost.Tags.Add(currentContext.Tags.Find(clientTag.Id));
                    }
                }

                //Only save changes if we modified the tags
                if (tagsModified)
                {
                    saveSuccess = currentContext.SaveChanges() > 0 ? true : false;
                }
            }
        }
    }

    if (saveSuccess != false)
    {
        return loadEditedPost(currentContext, newPost);
    }
    else
    {
        throw new JustBlogException($"Error saving changes to {newPost.Title}");
    }
}

PostService_Test.cs中的“SavePost_New_Post_Test”测试方法

/// <summary>
/// Test saving a new post
/// </summary>
[TestMethod]
public void SavePost_New_Post_Test()
{
    //Arrange
    var postSet_Mock = new Mock<DbSet<Post>>();

    var justBlogContext_Mock = new Mock<JustBlogContext>();
    justBlogContext_Mock.Setup(m => m.Posts).Returns(postSet_Mock.Object);

    IPostService postService = new PostService(justBlogContext_Mock.Object);

    // setup Test
    CategoryViewModel newCategory = new CategoryViewModel()
    {
        Description = "Category Description",
        Id = 0,
        Modified = new DateTime(),
        Name = "Name",
        PostCount = 0,
        Posts = new List<Post>(),
        UrlSlug = "Category Url Slug"
    };

    Tag newTag = new Tag()
    {
        Description = "Tag Description",
        Id = 1,
        Modified = new DateTime(),
        Name = "Tag Name",
        Posts = new List<Post>(),
        UrlSlug = "Url Slug"
    };

    Tag newTag2 = new Tag()
    {
        Description = "Tag Description 2",
        Id = 2,
        Modified = new DateTime(),
        Name = "Tag Name 2",
        Posts = new List<Post>(),
        UrlSlug = "UrlSlug2"
    };

    List<Tag> tags = new List<Tag>();

    tags.Add(newTag);
    tags.Add(newTag2);

    // setup new post
    PostViewModel newPost = new PostViewModel()
    {
        Category = newCategory,
        Description = "Test Descritpion",
        Meta = "Meta text",
        Modified = new DateTime(),
        Published = false,
        PostedOn = null,
        ShortDescription = "Short Description",
        Title = "TestTitle",
        UrlSlug = "TestUrlSlug",
        Tags = tags,
        Id = null
    };

    // counters to verify call order
    int addPostCount = 0;
    int addTagAttachCount = 0;
    int saveChangesCount = 0;
    int totalActionCount = 0;

    // Register callbacks to keep track of actions that have occured
    justBlogContext_Mock.Setup(x => x.Posts.Add(It.IsAny<Post>())).Callback(() =>
    {
        addPostCount++;
        totalActionCount++;
    });

    justBlogContext_Mock.Setup(x => x.SaveChanges()).Callback(() =>
    {
        saveChangesCount++;
        totalActionCount++;
    });

    justBlogContext_Mock.Setup(x => x.SaveChanges()).Returns(1);

    justBlogContext_Mock.Setup(x => x.Tags.Attach(It.IsAny<Tag>())).Callback(() =>
    {
        addTagAttachCount++;
        totalActionCount++;
    });          

    // Act
    postService.SavePost(newPost, false);
    // Assert
    justBlogContext_Mock.Verify(m => m.SaveChanges(), Times.AtLeastOnce());
    Assert.IsTrue(addPostCount == 1);
    Assert.IsTrue(addTagAttachCount == 2);
    Assert.IsTrue(totalActionCount == 4);
}

1 个答案:

答案 0 :(得分:4)

假设接口/抽象如

public interface IDbContext {
    int SaveChanges();
}

您可以像这样设置模拟。

var expected = 3;
var mock = new Mock<IDbContext>();
mock.Setup(m => m.SaveChanges()).Returns(expected);


var context = mock.Object;

var actual = context.SaveChanges();

Assert.AreEqual(expected, actual);