I have recently started using Optimizely Graph Client (Using the latest: 1.4.0). I have created a separate graph conventions field for each component that configures how the component will get indexed by the graph.
/// <summary>
/// Conventions for the accordion blocks
/// </summary>
public class AccordionGraphTypeConventions : IGraphTypeConventions
{
/// <summary>
/// Adds conventions for the accordion blocks
/// </summary>
/// <param name="conventionRepository">The conventions repository</param>
public void Configure(ConventionRepository conventionRepository)
{
// Ensure we have the repository
ArgumentNullException.ThrowIfNull(conventionRepository);
// Configure the accordion grouping block
conventionRepository?.ForInstancesOf<AccordionGroupingBlock>()?
.Set(p => p.Title, IndexingType.Searchable)
.Set(p => p.Description, IndexingType.OnlyStored)
.Set(p => p.IsFaq, IndexingType.Queryable)
.Set(p => p.Items, IndexingType.OnlyStored)
.ExcludeField(p => p.UniqueId);
// Configure the accordion item block
conventionRepository?.ForInstancesOf<AccordionItemBlock>()?
.Set(p => p.Title, IndexingType.Searchable)
.Set(p => p.Text, IndexingType.OnlyStored)
.ExcludeField(p => p.UniqueId);
}
}
This works fine. The problem I have encountered is when I try to write unit tests for this. In versions prior to 1.4.0 I was able to write a test like this:
public class AccordionGraphTypeConventionsTests : BaseTest
{
[Fact]
public void Configure_ConfigureAccordionGroupingBlock_ConfigurationIsSet()
{
// Arrange
var conventionRepository = Substitute.For<ConventionRepository>();
var accordionGraphConventions = new AccordionGraphTypeConventions();
// Act
accordionGraphConventions.Configure(conventionRepository);
// Assert
var fieldConventions = conventionRepository.GetFieldConventions(typeof(AccordionGroupingBlock));
foreach (var conventionType in fieldConventions)
{
var excludedFields = conventionType.GetExcludedFields();
var excludedFieldsArray = excludedFields as string[] ?? excludedFields.ToArray();
excludedFieldsArray.Length.Should().Be(1);
excludedFieldsArray.FirstOrDefault().Should().Be(nameof(AccordionGroupingBlock.UniqueId));
var indexedFields = conventionType.GetFieldsWithIndexingSetting();
var indexedFieldsArray = indexedFields as string[] ?? indexedFields.ToArray();
indexedFieldsArray.Length.Should().Be(4);
indexedFieldsArray.Should().Contain(nameof(AccordionGroupingBlock.Title));
indexedFieldsArray.Should().Contain(nameof(AccordionGroupingBlock.Description));
indexedFieldsArray.Should().Contain(nameof(AccordionGroupingBlock.IsFaq));
indexedFieldsArray.Should().Contain(nameof(AccordionGroupingBlock.Items));
conventionType.GetIndexingType(nameof(AccordionGroupingBlock.Title)).Should().Be(IndexingType.Searchable);
conventionType.GetIndexingType(nameof(AccordionGroupingBlock.Description)).Should().Be(IndexingType.OnlyStored);
conventionType.GetIndexingType(nameof(AccordionGroupingBlock.IsFaq)).Should().Be(IndexingType.Queryable);
conventionType.GetIndexingType(nameof(AccordionGroupingBlock.Items)).Should().Be(IndexingType.OnlyStored);
}
}
}
This way I can ensure that configurations work, and more importantly, it gives me the freedom to change things in the future and catch any problems that might arise. But after the update to 1.4.0 this has stopped working because of changes made to the ConventionRepository.
Specifically, the constructor and all the collections in the class are private and/or internal.
public class ConventionRepository
{
private readonly IDictionary<Type, ConventionType> _fieldConventions;
private readonly HashSet<Type> _includedInterfacesAndAbstractTypes;
private readonly HashSet<Type> _excludeTypes;
private readonly ConventionOnlyInclude _conventionOnlyInclude;
internal EventHandler<ConventionChangeEventArgs> OnConventionsChange;
internal ConventionRepository()
{
this._fieldConventions = (IDictionary<Type, ConventionType>) new Dictionary<Type, ConventionType>();
this._includedInterfacesAndAbstractTypes = new HashSet<Type>();
this._excludeTypes = new HashSet<Type>();
this._conventionOnlyInclude = new ConventionOnlyInclude(this);
}
.........
}
So any attempt to mock fails and I cannot get access to the internal collections. Is there any way that an interface could be extracted from this repository so that it could be mocked for a test? A lot of the other Optimizely repositories are set up this way.