You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Visual Studio中对SharePoint自定义Web部件执行NUnit单元测试?

如何为SharePoint自定义Web部件编写单元测试(针对Page_Load和按钮点击)

作为SharePoint开发者,我完全理解你想给Web部件加单元测试的需求——毕竟NUnit这类工具能帮我们提前发现问题,不用每次都部署到SharePoint环境里测试。SharePoint Web部件的测试难点在于它依赖SharePoint对象模型(SPWeb、SPList这些),直接跑测试会需要真实的SharePoint环境,所以我们得用**模拟框架(比如Moq)**来隔离这些依赖,结合你熟悉的NUnit来写测试用例。

下面我会结合你的场景,一步步给出可运行的示例:

第一步:改造Web部件代码,提升可测试性

首先,我们需要对原有的Web部件做一点小改造,让它更容易被测试——核心思路是把依赖的SharePoint对象抽成可替换的属性,避免直接硬编码SPContext.Current.Web。比如:

using Microsoft.SharePoint;
using System.Web.UI.WebControls;

public class MyCustomWebPart : WebPart
{
    // 声明控件(假设你已经在ascx或代码中初始化了这些控件)
    protected TextBox txtTitle;
    protected DropDownList ddlCategory;
    protected GridView gvItems;
    protected Button btnSave;

    // 可注入的SPWeb属性,测试时传入模拟对象
    private SPWeb _web;
    public SPWeb Web
    {
        get => _web ?? SPContext.Current.Web;
        set => _web = value;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            PopulateCategoryDropdown();
            BindItemsGridView();
        }
    }

    // 抽离下拉框填充逻辑,方便单独测试
    protected void PopulateCategoryDropdown()
    {
        var categoriesList = Web.Lists.TryGetList("Categories");
        if (categoriesList == null) return;

        ddlCategory.DataSource = categoriesList.Items.GetDataTable();
        ddlCategory.DataTextField = "Title";
        ddlCategory.DataValueField = "ID";
        ddlCategory.DataBind();
    }

    // 抽离GridView绑定逻辑
    protected void BindItemsGridView()
    {
        var itemsList = Web.Lists.TryGetList("Items");
        if (itemsList == null) return;

        var query = new SPQuery { Query = "<OrderBy><FieldRef Name='Title' Ascending='True'/></OrderBy>" };
        var items = itemsList.GetItems(query);
        
        gvItems.DataSource = items.GetDataTable();
        gvItems.DataBind();
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        var itemsList = Web.Lists.TryGetList("Items");
        if (itemsList == null) return;

        var newItem = itemsList.AddItem();
        newItem["Title"] = txtTitle.Text;
        newItem["Category"] = new SPFieldLookupValue(int.Parse(ddlCategory.SelectedValue));
        newItem.Update();
    }
}

第二步:编写单元测试用例(NUnit + Moq)

首先,你需要安装必要的NuGet包:NUnit、Moq、Microsoft.SharePoint(如果你的项目已经引用了SharePoint SDK,可以忽略)。

接下来是测试类,我们会覆盖Page_Load首次加载、GridView绑定、按钮保存这几个核心场景:

using NUnit.Framework;
using Moq;
using Microsoft.SharePoint;
using System;
using System.Web.UI.WebControls;
using System.Reflection;

[TestFixture]
public class MyCustomWebPartTests
{
    private Mock<SPWeb> _mockWeb;
    private Mock<SPList> _mockCategoriesList;
    private Mock<SPList> _mockItemsList;
    private MyCustomWebPart _webPart;
    private DropDownList _mockDdlCategory;
    private GridView _mockGvItems;

    // 每个测试前初始化模拟对象
    [SetUp]
    public void Setup()
    {
        // 初始化SharePoint对象的模拟实例
        _mockWeb = new Mock<SPWeb>();
        _mockCategoriesList = new Mock<SPList>();
        _mockItemsList = new Mock<SPList>();

        // 设置Web的Lists.TryGetList返回模拟的列表
        _mockWeb.Setup(w => w.Lists.TryGetList("Categories")).Returns(_mockCategoriesList.Object);
        _mockWeb.Setup(w => w.Lists.TryGetList("Items")).Returns(_mockItemsList.Object);

        // 创建Web部件实例,注入模拟的SPWeb
        _webPart = new MyCustomWebPart();
        _webPart.Web = _mockWeb.Object;

        // 初始化模拟控件(因为控件是protected,用反射赋值)
        _mockDdlCategory = new DropDownList();
        _mockGvItems = new GridView();

        AssignProtectedField(_webPart, "ddlCategory", _mockDdlCategory);
        AssignProtectedField(_webPart, "gvItems", _mockGvItems);
    }

    // 辅助方法:给protected字段赋值
    private void AssignProtectedField(object target, string fieldName, object value)
    {
        var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(target, value);
    }

    #region Page_Load测试用例
    [Test]
    public void Page_Load_OnFirstLoad_PopulatesCategoryDropdownCorrectly()
    {
        // 1. 准备测试数据:模拟Categories列表的项
        var mockItems = new Mock<SPListItemCollection>();
        var categoryTable = new System.Data.DataTable();
        categoryTable.Columns.Add("ID", typeof(int));
        categoryTable.Columns.Add("Title", typeof(string));
        categoryTable.Rows.Add(1, "文档类");
        categoryTable.Rows.Add(2, "图片类");
        mockItems.Setup(i => i.GetDataTable()).Returns(categoryTable);
        _mockCategoriesList.Setup(l => l.Items).Returns(mockItems.Object);

        // 2. 模拟Page不是回发状态
        var mockPage = new Mock<System.Web.UI.Page>();
        mockPage.Setup(p => p.IsPostBack).Returns(false);
        _webPart.Page = mockPage.Object;

        // 3. 执行Page_Load方法
        _webPart.Page_Load(null, EventArgs.Empty);

        // 4. 断言下拉框已正确绑定数据
        Assert.AreEqual(2, _mockDdlCategory.Items.Count);
        Assert.AreEqual("文档类", _mockDdlCategory.Items[0].Text);
        Assert.AreEqual("1", _mockDdlCategory.Items[0].Value);
    }

    [Test]
    public void Page_Load_OnFirstLoad_BindsItemsGridView()
    {
        // 1. 模拟Items列表的查询结果
        var mockQueryItems = new Mock<SPListItemCollection>();
        var itemsTable = new System.Data.DataTable();
        itemsTable.Columns.Add("Title", typeof(string));
        itemsTable.Rows.Add("项目文档1");
        itemsTable.Rows.Add("项目图片1");
        mockQueryItems.Setup(i => i.GetDataTable()).Returns(itemsTable);
        _mockItemsList.Setup(l => l.GetItems(It.IsAny<SPQuery>())).Returns(mockQueryItems.Object);

        // 2. 模拟非回发状态
        var mockPage = new Mock<System.Web.UI.Page>();
        mockPage.Setup(p => p.IsPostBack).Returns(false);
        _webPart.Page = mockPage.Object;

        // 3. 执行Page_Load
        _webPart.Page_Load(null, EventArgs.Empty);

        // 4. 断言GridView数据源正确
        var dataSource = _mockGvItems.DataSource as System.Data.DataTable;
        Assert.IsNotNull(dataSource);
        Assert.AreEqual(2, dataSource.Rows.Count);
    }
    #endregion

    #region 按钮点击测试用例
    [Test]
    public void btnSave_Click_CreatesAndUpdatesNewListItem()
    {
        // 1. 模拟输入控件的值
        var mockTxtTitle = new TextBox { Text = "新测试项" };
        AssignProtectedField(_webPart, "txtTitle", mockTxtTitle);
        _mockDdlCategory.SelectedValue = "1";

        // 2. 模拟列表AddItem返回新的ListItem
        var mockNewItem = new Mock<SPListItem>();
        _mockItemsList.Setup(l => l.AddItem()).Returns(mockNewItem.Object);

        // 3. 执行按钮点击事件
        _webPart.btnSave_Click(null, EventArgs.Empty);

        // 4. 断言新项的属性已设置,且Update方法被调用
        mockNewItem.Verify(i => i["Title"] = "新测试项", Times.Once);
        mockNewItem.Verify(i => i["Category"] = It.IsAny<SPFieldLookupValue>(), Times.Once);
        mockNewItem.Verify(i => i.Update(), Times.Once);
    }
    #endregion
}

关键注意事项

  1. 隔离SharePoint依赖:通过Web属性注入SPWeb,避免直接依赖SPContext.Current,这样测试时可以用Moq模拟SPWeb、SPList等对象,不需要真实的SharePoint环境。
  2. 反射访问protected控件:Web部件的控件通常是protected的,测试时需要用反射来设置或验证这些控件的状态。
  3. 模拟Page状态:通过Mock<Page>来控制IsPostBack属性,测试不同的Page_Load分支逻辑。
  4. 验证方法调用:对于保存逻辑,我们不需要验证数据是否真的写入SharePoint,只需要验证AddItem()、Update()等方法是否被正确调用,以及参数是否符合预期。

内容的提问来源于stack exchange,提问作者Sharp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 19:17:39