我一直在使用moles在我难以访问的代码部分上编写单元测试 - 特别是在我使用sitecore类库中找到的类型(很难使用模拟框架)的情况下。我遇到了一个令人惊讶的棘手问题,我试图扼杀LinkField类型并测试以下类型的代码片段。
LinkField linkField = item.Fields["External Url"];
if (linkField.IsInternal && linkField.TargetItem != null)
{
// want to test this path
item.Fields是一个字段集合,其索引器返回SiteCore.Data.Field的类型,但是LinkField设置为使用隐式转换的隐式运算符,这意味着您可以使用LinkField的实例你的代码。
我的困难在于我无法创建MLinkField类型的mole,并将其分配给Item中的FieldCollection,因为它是强类型为Field。而且,似乎虽然我能够创建一种类型的MField,但是当隐式转换发生时,它将工作并返回一个对象,但没有一个字段被设置为具有任何值。这意味着,我无法测试上面依赖于以某种方式设置的linkField状态的代码路径。
我能想到设置这些值的唯一方法是间接 - 即通过分析隐式转换并在MField中设置这些值来找到需要设置的值。隐式运算符调用LinkField构造函数,如下所示:
public LinkField(Field innerField) : base(innerField, "link")
这意味着我需要意识到它如何实例化基类型(XmlField),以及该类的基类型(CustomField)。 然后,查看TargetItem正在寻找的基础值。最终结果是需要扼杀:
InnerField.Database.Items[internalPath];
或者
InnerField.Database.Items[targetID];
InnerField实际上是我的MField。
有没有人有更好的主意?这听起来令人费解,但我认为这是这些集会的野兽的本质。
答案 0 :(得分:3)
可以这样做,但你需要跳过一些箍才能使它发挥作用。
首先,有点背景:
现在一些代码:
const string externalUrl = "External Url";
const string targetItemName = "Target Item";
Field field = new ShimField { IDGet = () => ID.NewID, NameGet = () => externalUrl };
Item targetitem = new ShimItem { IDGet = () => ID.NewID, NameGet = () => targetItemName };
LinkField linkfield = new ShimLinkField(field) { IsInternalGet = () => true, TargetItemGet = () => targetitem };
ShimLinkField.ImplicitOpFieldLinkField = (f) => linkfield;
FieldCollection fields = new ShimFieldCollection { ItemGetString = (name) => linkfield.InnerField };
Item item = new ShimItem { NameGet = () => "Test Item", FieldsGet = () => fields };
现在进行一些解释:
使上述代码工作的关键是:
ShimLinkField.ImplicitOpFieldLinkField = (f) => linkfield;
通过Shimming隐式转换运算符,我们可以确保在调用以下行时:
LinkField linkField = item.Fields["External Url"];
返回一个链接字段,并根据需要对其属性进行填充。
答案 1 :(得分:1)
由于LinkField
无法以适当的方式进行模拟,因此必须“按原样”在单元测试中使用,这意味着您必须测试代码和链接字段实现。
好消息是Link字段是XmlField
,它使用存储在内部字段值中的xml数据进行操作。为了配置链接字段行为,您只需在值中设置适当的xml。
使用Sitecore.FakeDb,您可以轻松地在内存中创建内容并配置所需的项目和字段。以下代码创建项 home 和 target 。 主页项目有 Url 链接字段,其中TargetId
指向目标项目:
ID targetId = ID.NewID;
using (var db = new Db
{
new DbItem("home")
{
// Field 'Url' is an internal link which targets the 'target' item
{ "Url", "<link linktype=\"internal\" id=\"{0}\" />".FormatWith(targetId) }
},
new DbItem("target", targetId)
})
{
Item item = db.GetItem("/sitecore/content/home");
LinkField linkField = item.Fields["Url"];
linkField.IsInternal.Should().BeTrue();
linkField.TargetItem.Should().NotBeNull();
linkField.TargetItem.Paths.FullPath.Should().Be("/sitecore/content/target");
}
答案 2 :(得分:0)
就个人而言,我确实尝试将Sitecore从不需要直接与之交互的业务逻辑中抽象出来;但是我注意到有些东西需要直接处理,我尽量不要太过分。如果你花费太多时间从你的逻辑中抽象Sitecore,那么在我看来,你最终可能会更难以利用一些使Sitecore有用的功能。
虽然单元测试纯粹主义者可能不同意我的方法,但我将使用Sitecore API进行单元测试以正确实例化项目。这样做是相当直接的,我刚才写了a blog post。那么你唯一的问题是处理测试数据,但使用测试设置创建测试数据相当容易,并通过测试拆除将其删除。