Sitecore 7: A developers quest to mastering unit testing
-
Upload
nonlinear-creations -
Category
Technology
-
view
1.930 -
download
0
Transcript of Sitecore 7: A developers quest to mastering unit testing
And how I went through the 4 stages of developer denial
Prepared by: Jason St-Cyr
Microsoft Fakes
Sitecore Dev team unit testing prototype:
https://github.com/Sitecore/sitecore-seven-unittest-example
Nextdigital blog: Shooting for the Sky:
http://www.nextdigital.com/voice/shooting-for-the-sky
Stackoverflow: Unit Testing IQueryable operations:
http://stackoverflow.com/questions/20863942/unit-testing-iqueryable-operations
Stage 1: Egotism
Stage 2: Confusion
Sitecore Resources…
• Sitecore Dev team unit testing prototype: https://github.com/Sitecore/sitecore-seven-unittest-example
From the community…
• Nextdigital blog: Shooting for the Sky:http://www.nextdigital.com/voice/shooting-for-the-sky
• Stackoverflow: Unit Testing IQueryable operations:http://stackoverflow.com/questions/20863942/unit-testing-iqueryable-operations
[TestMethod]public void GetAll(){
//A small data set of entities with different Template IDsvar data = new List<SearchResultItem>(){
new CaseStudy { TemplateId = CaseStudy.TemplateId, Title = "Match 1", TeaserText = "Match 1 is awesome.", Name = "Match-1" },new CallToActionGeneral { TemplateId = CallToActionGeneral.TemplateId, Content = "This is not a match. This is a call to action", Name = "Not-a-match" },new CaseStudy { TemplateId = CaseStudy.TemplateId, Title = "Match 2", TeaserText = "Match 2 is kind of neat.", Name = "Match-2" },
};
//Create the fake index that will be used to replace the standard Sitecore indexvar index = CreateFakeIndex<SearchResultItem>(data);
//Create the provider that will be testedvar provider = new CaseStudyProvider(index);
//Test the GetAll method, ensuring we return all possible results.var caseStudies = provider.GetAll(data.Count);
//Verify that only 2 of the 3 returned.Assert.AreEqual(caseStudies.Count(), 2);
//Verify that the 2 returned are the correct onesforeach (CaseStudy caseStudy in caseStudies){
var isMatch1 = caseStudy.Name != null && caseStudy.Name.ToLower().Equals("match-1");var isMatch2 = caseStudy.Name != null && caseStudy.Name.ToLower().Equals("match-2");
Assert.IsTrue(isMatch1 || isMatch2, "Result {0} is not expected.", caseStudy.Name);}
}
Test Data
Faking the Index
Running the test
public class CaseStudyProvider : ICaseStudyProvider{
private ISearchIndex Index { get; set; }
public CaseStudyProvider(ISearchIndex index) {Index = index;
}
/// <summary>/// Searches the index for components that are of type 'Case Study' and returns the entity/// </summary>/// <param name="numberOfStudies">The maximum number of studies to return</param>/// <returns>A list of CaseStudy entities</returns>public IEnumerable<CaseStudy> GetAll(int numberOfStudies){
//Get the queryable that will be used to get the case studiesvar context = Index.CreateSearchContext();var queryable = context.GetQueryable<SearchResultItem>();
return queryable.Where(item => item.TemplateId == CaseStudy.TemplateId).Take(numberOfStudies).Cast<CaseStudy>();}
}
Based on the UpcomingEventsProvider by Dan Solovay in
his Stack Overflow post.
Simplified to only return the items in the index
with the correct Template ID.
Sitecore Example Fixture (FakeItEasy):[SetUp]public void Setup(){
this.fakeIndex = A.Fake<ISearchIndex>();this.fakeSearchContext = A.Fake<IProviderSearchContext>();
A.CallTo(() => this.fakeIndex.CreateSearchContext(SearchSecurityOptions.DisableSecurityCheck)).Returns(this.fakeSearchContext);}
Dan Solovay Example (NSubstitute):private static ISearchIndex MakeSubstituteIndex(List<EventPageSearchItem> itemsToReturn){
ISearchIndex index = Substitute.For<ISearchIndex>();_eventTemplateId = MyProject.Library.IEvent_PageConstants.TemplateId;
SimpleFakeRepo<EventPageSearchItem> repo = newSimpleFakeRepo<EventPageSearchItem>(itemsToReturn);index.CreateSearchContext().GetQueryable<EventPageSearchItem>().Returns(repo);return index;
}
• My good buddy Joe comes by…
• And then I’m all like…
Stage 3: Frustration
/// <summary>/// The Fake Provider Search Context will always return the repository assigned to it/// </summary>public class FakeProviderSearchContext : IProviderSearchContext{
/// <summary>/// Storage for repository used by this instance of the context/// </summary>public IQueryable Repository { get; set; }
/// <summary>/// Returns the repository/// </summary>/// <typeparam name="TItem"></typeparam>/// <returns></returns>public IQueryable<TItem> GetQueryable<TItem>() where TItem : new(){
return Repository.Cast<TItem>();}
…
Storing data
Faking Data Return
Right?
• At some point during the flow, even if explicitly set, the search context object was going null.
• That means no results.
• …no success.
• …full of anger.
• …desire to push forward, so close.
Stage 4: Success
Doesn’t
work
Fake
Context
Fake
Index
Fake
Index
Fake
ContextWorks!
/// <summary>/// Represents the fake index. Can be bound to a repository so that /// all search contexts created will bind to this repository./// </summary>public class FakeSearchIndex : ISearchIndex{
/// <summary>/// Storage for the repository data used on the index/// </summary>public IQueryable Repository { get; set; }
/// <summary>/// Implement a fake search context creation which will allow us to persist/// a fake data associated to the index/// </summary>/// <param name="options"></param>/// <returns></returns>public IProviderSearchContext CreateSearchContext(SearchSecurityOptions options = SearchSecurityOptions.EnableSecurityCheck){
var context = new FakeProviderSearchContext();context.Index = this;context.SecurityOptions = options;context.Repository = Repository;
return context;}
…
Store the data
Create each context
with the data
/// <summary>/// Create a fake index that contains the data specified/// </summary>/// <param name="items"></param>/// <returns></returns>private static ISearchIndex CreateFakeIndex<T>(List<T> items) where T:SearchResultItem{
var repository = new FakeRepository<T>(items);
var index = new FakeSearchIndex();index.Repository = repository;
return index;} Set the data
Once the data was moved to the Index object,
each context was successfully created, and the
test passed!
• A single FakeSearchIndex class can represent any index.
• It only takes about 20 lines of actual code to implement the fake objects for the index, context, and repository.
• Need to have unit tests create sample data to fill the fake index.
• Sitecore.ContentSearch and Sitecore.ContentSearch.SearchTypes are your namespace friends.
• No mocking needed to test business logic that queries indexes!
- Limited use: can only support testing queries on the context, unless the rest of the ISearchIndex implementation is built out.
Jason St-Cyr
Solution Architect, nonlinear digital
Background in .NET software development and Application Lifecycle
Management
Contact me: [email protected]
Around the web:
Technical Blog:
http://theagilecoder.wordpress.com
LinkedIn Profile:
http://www.linkedin.com/pub/jason-st-cyr/26/a73/645
Nonlinear Thinking:
http://blog.nonlinearcreations.com/en/author/jason-st-cyr/
Twitter: @AgileStCyr