How to Test SiteCore LinkField Effectively

I use moles to write unit tests on hard-to-reach parts of my code, especially when I use types found in sitecore class libraries (which are hard to use mocking frameworks with). I ran into a surprisingly difficult problem when Im trying to unwind a LinkField type and test the following kind of code snippet.

LinkField linkField = item.Fields["External Url"]; if (linkField.IsInternal && linkField.TargetItem != null) { // want to test this path 

item.Fields is a collection of fields whose index returns the type SiteCore.Data.Field, but the LinkField is configured in such a way that it uses an implicit operator that hides the conversion, which means that you can work with the LinkField instance in your code.

My difficulty is that I cannot create a mole of type MLinkField and assign it to FieldCollection in Item, since it is strongly typed for Field. Moreover, it seems that although I can create an MField type when an implicit conversion occurs, it will work and return an object, but none of the fields have any values. This means that I cannot check the code path above, which depends on the state of the linkField property defined in a certain way.

The only way I can think of setting these values ​​indirectly is to search for which values ​​to set by analyzing the implicit conversion and setting them to MField. The implicit statement calls the LinkField constructor as follows:

 public LinkField(Field innerField) : base(innerField, "link") 

which means that I need to be aware of how it creates the base type (XmlField) and, in turn, this base class type (CustomField). Then, to see what basic values ​​TargetItem is looking for. The end result is the need to extort:

 InnerField.Database.Items[internalPath]; 

or

 InnerField.Database.Items[targetID]; 

Where InnerField is effectively my MField.

Anyone have a better idea? It sounds terribly confusing, but I think the nature of the beast is with these collections.

+4
source share
3 answers

This can be done, but you need to jump over several hoops for them to work.

First of all, a little background:

  • LinkField does not inherit from the Field class, as some other field types do.
  • LinkField inherits from XmlField, which in turn inherits from CustomField.
  • CustomField (and its subtypes) are created by passing the field instance to the constructor.
  • Linkfields store their values ​​in this field instance.
  • Linklinds cannot be added to FieldCollection, as they are not inherited from the field.
  • Instead of adding a LinkField, we need to add the LinkField InnerField property.
  • When a field is retrieved from a FieldCollection and assigned a LinkField, an implicit conversion operation is performed.
  • The implicit conversion operation creates a new LinkField, passing the selected field to the LinkField constructor. This is the new LinkField that is coming back.

Now some code:

 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 }; 

And now for a little explanation:

The key to creating the above code is the line:

 ShimLinkField.ImplicitOpFieldLinkField = (f) => linkfield; 

By shimming the implicit conversion operator, we can guarantee that when you call the following line:

 LinkField linkField = item.Fields["External Url"]; 

The link field is returned and the properties will be scanned the way you want.

+3
source

Since LinkField cannot be mocked correctly, it should be used in unit tests "as is", which means that you will need to check both your code and the implementation of the link field.

The good news is that the Link field is an XmlField that works with xml data stored in an internal field. To customize the behavior of the link field, you just need to set the correct xml in the value.

With Sitecore.FakeDb, you can easily create content in memory and customize the elements and fields that you need. The following code creates the home and target elements. The home element has a URL link field that TargetId points to the target:

  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"); } 
+1
source

Personally, I am trying to distract Sitecore from business logic that does not require direct interaction with it; however, I remember that some things will have to deal with him directly, and I try not to go too far. If you spend too much time abstracting Sitecore out of your logic, then, in my opinion, you may end up complicating the use of some of the features that make Sitecore useful.

While purists with unit testing may disagree with my approach, I will have unit tests that use the Sitecore API to properly instantiate elements. This is pretty straight forward, I wrote a blog post about this a while ago. Then your only problem is with the test data, but it is quite easy to create it using the test setup and delete it using the test stall.

0
source

Source: https://habr.com/ru/post/1444147/


All Articles