This post has been migrated from www.experimentsincode.com, we apologise if some of the images or content is missing
Posts in this series:public partial class AddComment : System.Web.UI.Page { protected override void OnInit(EventArgs e) { submit.Click += new EventHandler(submit_Click); base.OnInit(e); } void submit_Click(object sender, EventArgs e) { //a few presets for a comment Guid commentTemplate = new Guid("{73888B25-314C-4F93-8F7C-48E916C054C9}"); DateTime commentDate = DateTime.Now; string commentName = string.Format("comment-{0}", commentDate.ToString("dd-MM-yyyy-hh-mm-ss")); //create a new comment Item parent = Sitecore.Context.Database.GetItem("/sitecore/content/home/news/interestingarticle"); Item newComment = parent.Add(commentName, new TemplateID(new ID(commentTemplate))); //populate the comment newComment.Editing.BeginEdit(); newComment["username"] = username.Text; newComment["comment"] = comment.Text; newComment["email"] = comment.Text; newComment["date"] = DateUtil.ToIsoDate(commentDate); newComment.Editing.EndEdit(); } }So we can turn this into something testable using the MVP pattern. We can create a presenter and a view interface:
public class CommentPresenter { ICommentView _view; Database _database; public CommentPresenter(ICommentView view, Database database) { _view = view; _database = database; } public void SubmitComment() { try { //a few presets for a comment Guid commentTemplate = new Guid("{73888B25-314C-4F93-8F7C-48E916C054C9}"); DateTime commentDate = DateTime.Now; string commentName = string.Format("comment-{0}", commentDate.ToString("dd-MM-yyyy-hh-mm-ss")); //create a new comment Item parent = _database.GetItem(_view.ParentItem); using (new SecurityDisabler()) { Item newComment = parent.Add(commentName, new TemplateID(new ID(commentTemplate))); //populate the comment newComment.Editing.BeginEdit(); newComment["username"] = _view.Username; newComment["comment"] = _view.Comment; newComment["email"] = _view.Email; newComment["date"] = DateUtil.ToIsoDate(commentDate); newComment.Editing.EndEdit(); } _view.Action(CommentViewAction.CommentAdded); } catch (Exception ex) { _view.ErrorMessage = ex.Message; _view.Action(CommentViewAction.ErrorMessage); } } } public enum CommentViewAction { CommentAdded, ErrorMessage } public interface ICommentView { string ParentItem { get; } string Comment { get; } string Username { get; } string Email { get; } string ErrorMessage { set; } void Action(CommentViewAction action); }With these classes we can then modify the sub-layout to use the presenter. We change the sub-layout to inherit from the comment interface, then on the page load we instantiate the presenter passing in the sub-layout and the Sitecore context database into the presenter. The sub-layout then becomes very thin with all the logic in the presenter.
public partial class AddComment : System.Web.UI.Page, ICommentView { CommentPresenter _presenter; protected override void OnInit(EventArgs e) { _presenter = new CommentPresenter(this, Sitecore.Context.Database); submit.Click += new EventHandler(submit_Click); base.OnInit(e); } void submit_Click(object sender, EventArgs e) { _presenter.SubmitComment(); } #region ICommentView Members public string ParentItem { get { return "/sitecore/content/home/news/interestingarticle"; } } public string Comment { get { return comment.Text; } } public string Username { get { return username.Text; } } public string Email { get { return email.Text; } } public string ErrorMessage { get; set; } public void Action(CommentViewAction action) { switch (action) { //Just quickly output the results case CommentViewAction.CommentAdded: Response.Write("Comment has been added"); break; case CommentViewAction.ErrorMessage: Response.Write(string.Format("An error as occured, {0}", ErrorMessage)); break; } } #endregion }Now we have a separation between presentation and logic we can test the logic in the presenter works how we think it should. Within the test project we can create the following test fixture:
[TestFixture] public class CommentPresenterFixture { [Test] public void SubmitComment_CreatesAComment() { string comment = "testComment"; string email = "testEmail"; string username = "testUsername"; string parent = "/sitecore/content/home/news/interestingarticle"; Database db = Sitecore.Configuration.Factory.GetDatabase("master"); Mock view = new Mock(); view.Setup(x => x.Comment).Returns(comment); view.Setup(x => x.Email).Returns(email); view.Setup(x => x.Username).Returns(username); view.Setup(x => x.ParentItem).Returns(parent); CommentPresenter presenter = new CommentPresenter(view.Object, db); presenter.SubmitComment(); view.Verify(x => x.Action(CommentViewAction.CommentAdded)); view.VerifyAll(); Item parentItem = db.GetItem(parent); //not the best line I admit, but there are limits to what we can do Item newChild = parentItem.Children.Cast().Last(); Assert.AreEqual(comment, newChild["comment"]); Assert.AreEqual(email, newChild["email"]); Assert.AreEqual(username, newChild["username"]); } }So in this class we mock the view interface so that it returns my predefined values; for the mocking I have used Moq. I also instantiate a Sitecore database using the Sitecore factory.  You can then see that the rest of the testing is quite straight forward. Running the test should hopefully return lots of green lights. The only line that I am not particularly happy with is the following:
Item newChild = parentItem.Children.Cast().Last();Unfortunately at the moment I can't think of a more sensible way to get the item that was created by Sitecore. So we now have system that give us red/green testing of modifications to Sitecore, we can now properly test our interactions with Sitecore. While doing this sort of testing you might want to setup a second instance of Sitecore  that your tests run against so that your development instance does not get polluted by test items. You could also add clean up code to the end of each tests, in the example above we could delete the comment item after we have finished the tests.