Part 3–SharePoint Retreat–TDD and Unit testing, the methodology used cont..

The other parts of this series can be found here…

  1. SPRetreat TDD – a retrospective (See what I did there? -)
  2. Part 1 — SharePoint Retreat–TDD and Unit Testing, the methodology used
  3. Part 2 — SharePoint Retreat–TDD and Unit Testing, the methodology used cont..
  4. Part 3 -– SharePoint Retreat-TDD and Unit Testing, the methodology used cont..  (This post)

Moving into the realms of SharePoint

In the previous episode, we created our Magic8Ball object using TDD techniques to drive the creation of the Objects, Properties, Fields and methods. We now need to take that completed object and look at how we bring it into the SharePoint environment, utilising TDD whre possible. (Note: I’m focussing on TDD here, not SharePoint, therefore this is NOT production standard code.)

We didn’t get quite as far as getting our Magic8Balls fully into SharePoint on the retreat, other than to use TypeMock to isolate us from calls to the object model. Therefore this part will hopefully generate a bit of retrospective discussion as I’m going to try and adapt what I learnt about TDD on the Retreat and apply it here, without the benefit of the retrospectives we held.

If I get this wrong, it will in any case reinforce the need to hold Agile retrospectives when possible! Winking smile

The important thing to remember is that we don’t need to use TDD on everything in this project. We’re going to use the supplied distribution framework from MS for instance, and one hopes that they’ve tested this thoroughly, therefore we won’t.. Crossing the Seams… remember?image

In order to help this process, I’m going to create a new SharePoint aware class that inherits from Magic8Ball. I can then use this class simply in a Sharepoint web part project, keeping the logic and display separation as much as possible. We’ll add the corresponding test class to the test project at the same time.

Remember to decorate your test class with the TestFixture attribute so that NUnit knows it has tests in it. Let Resharper handle the Using statements for you, it’s quicker!

image

At this point, I forgot to make the SharePoint8Ball class public, but Resharper spotted that and offers to fix it..

image

We’ve allowed Resharper to do this and we can now reference the class in our test code.

            SharePoint8Ball spball = new SharePoint8Ball();
            spball.Answers = new List<string>() {"a", "b", "c"};

At the moment though, we have a compile failure as the spball object doesn’t have an Answers property. In this case though, Resharper can’t help us at it’s only suggesting that we create a new property or field. What we actually want to do is have the SharePoint8Ball class inherit from the Magic8Ball class. As this is a compile failure, it’s perfectly valid in the TDD mantra to switch to the production code and correct this.

    public class SharePoint8Ball : Ball
    {
    }

This fixes our compile failure as the spball now has an Answers property. We’ll finish our test code by repeating an earlier test, but this time with the new object.

        [Test]
        public void Create_NewSP8ball_ReturnsValidBall()
        {
            //Arrange
            SharePoint8Ball spball = new SharePoint8Ball();
            spball.Answers = new List<string>() {"a", "b", "c"};

            //Act
            string answers = spball.AskQuestion("q");

            //Assert
            Assert.Contains(answers,spball.Answers);

        }

Run all the tests and you should get all passed.

------ Test started: Assembly: Magic8Ball.Test.dll ------

11 passed, 0 failed, 0 skipped, took 12.46 seconds (NUnit 2.5.5).

Our next step is to think about the new SharePoint8Ball and how it differs from it’s Parent the Magic8Ball. We still have exactly the same demands of the object, We want to supply a list of Answers, We want to ask a  question and we want a single life changing answer (well an answer at least..) We don’t need to retest everything that we tested previously as we know that we’re talking to the parent object happily.  So what’s different?

  • We want to talk to a SharePoint Web.
  • We want to get the answers from a List.
  • The administrator will provide the List name in the web part properties.

So, at a bare minimum we need to handle an SPWeb object and a Listname. We’ll start with the SPWeb. As we’re planning to use this object in a Web Part, we can use the SPContext object to get access to the current Web object.

Just as an aside, I found myself starting to cut and paste the Setup code this time round for the SharePoint8Ball, so it’s time to refactor, a step I missed after the first test! So we’ll quickly make use of the NUnit [Setup] tag. this denotes a block of code to be run before every test in that class.

At the top of our SharePoint8BallTest class we’ll add a private field for SharePoint8Ball spball, We’ll add a new Method called SetUp which will be decorated with the [SetUp] attribute and we’ll refactor out the //Arrange frm our previous test, giving us the code below.

        private SharePoint8Ball spball;
        
        [SetUp]
        public void SetUp()
        {
            //Arrange
            spball = new SharePoint8Ball();
            spball.Answers = new List<string>() {"a", "b", "c"};
        }

        [Test]
        public void Create_NewSP8ball_ReturnsValidBall()
        {
            //Act
            string answers = spball.AskQuestion("q");

            //Assert
            Assert.Contains(answers,spball.Answers);

        }

Running our test as before passes.

Where were we? Ah yes we want to get an SPWeb object into a SharePoint8Ball property. Time for another test.

As we already have an //Arrange section in the set-up and don’t need to add anything this is a very short test as the //Act is also in the assert.

        [Test]
        public void Create_NewSP8Ball_ReturnsValidSPWeb()
        {
            //Arrange
            //Act
            //Assert
            Assert.IsNotNull(spball.Web);
        }

Straight away we have a compile failure on the spball.Web object as it doesn’t exist. I’m going to fix this by allowing Resharper to create a Read-only property as I want to create the SPWeb in the constructor for the SharePoint8Ball class.

image

Resharper creates our Public read-only property and then I’ve added a private field to sit behind. but, we’ve hit a compile error as the Class isn’t currently aware of the SharePoint namespace yet.

image

Unfortunately Resharper can’t fix this just yet as we don’t have a reference to Microsoft.SharePoint.dll in our class. Once we add the reference, we can let’s Resharper add the using statements.

image

Running the test returns a fail as we hit an exception. Fix that by changing the Property to return the internal web field. Run the test again and this time it fails with a null reference exception. To fix this one, we need to get busy on the SharePoint8Ball constructor itself. As we’re planning to use the SPContext object we can add a simple constructor that takes a reference to the SPContext.Current.Web object and places this in the internal .Web field.

        public SharePoint8Ball()
        {
            web = SPContext.Current.Web;
        }

At this point, running the test again still results in a Null reference exception, this time however we know that it’s because we’re running outside of the Web environment, therefore SPContext doesn’t exist. To test this we’ll have to use TypeMock to fake the objects.

In our failing test, we need to add some extra code to the //Arrange portion of our failing test. Start by typing SPWeb and let Resharper add both the reference and the using statements this time.

image

Add an object name fakeWeb = Isolate. and then let Resharper do some work again, this time adding the Using statement for TypeMock.

image

We’re all set up now for the faking of SharePoint objects so the code block below can be added.

        [Test]
        public void Create_NewSP8Ball_ReturnsValidSPWeb()
        {
            //Arrange            
            SPWeb fakeWeb = Isolate.Fake.Instance<SPWeb>();
            Isolate.WhenCalled(() => SPContext.Current.Web).WillReturn(fakeWeb);

            //Act
            //Assert
            Assert.IsNotNull(spball.Web);
        }

With this code in and running the test, I hit a problem. I got a Null Reference exception that I wasn’t expecting. This should have worked fine. After a quick debug, I realised that my SetUp code was of course calling the new constructor before the typeMock and as a result was failing because it couldn’t see the faked SPContext object this early on. This mean’t both tests were failing.

Therefore as I’d also need the faked SPContext object in the first test now, I refactored the return of fakeWeb into my setup method and moved the creation of the Answers string list into Test one as it needs to overide anything we set-up in the constructor.

        public void SetUp()
        {
            //Arrange
            SPWeb fakeWeb = Isolate.Fake.Instance<SPWeb>();
            Isolate.WhenCalled(() => SPContext.Current.Web).WillReturn(fakeWeb);

            spball = new SharePoint8Ball();
        }

Note the order in this code, the Faked objects are constructed BEFORE the SharePoint8Ball. Run the tests again and both pass.

By hitting this problem, I realised that I’m going to have to do ALL of my TypeMock setting up in the setup method now, as I’ll be making changes to the constructor as I go which will affect both tests. the alternative would be to mark the first test as ignore, remove the Setup and place it all back in the tests again.

I decided that as I want to perhaps vary some of the later tests, I’d ensure all the mocks are configured in the Setup.

At this point, I’d played with the test code quite a bit whilst I worked out how to do this. What was quite reassuring was how easy it was to just run all the tests on the project once I got back to the code state I thought I wanted to be in. Seeing all the unit tests pass was a great confidence booster.

So for the next test, I want to retrieve answers from a SharePoint list and place these in the spball.Answers object. To do this, we’re going to need to fake a couple of new items. We need a fakeList and a fakeItem.

We’ll add the following code to our SetUp between the creation of the fakeWeb and the initialisation of our spball object.

            SPList fakeList = Isolate.Fake.Instance<SPList>();
            Isolate.WhenCalled(() => fakeWeb.Lists["Listname"]).WillReturn(fakeList);

            SPListItem fakeItem = Isolate.Fake.Instance<SPListItem>();
            Isolate.WhenCalled(() => fakeItem.Title).WillReturn("z");
            Isolate.WhenCalled(() => fakeList.Items).WillReturnCollectionValuesOf(new List<SPListItem>
            {
                fakeItem
            });

We then create our test against the Answers object from SharePoint.

        [Test]
        public void Create_NewSP8Ball_RetrieveAnswersFromSPList()
        {
            //Arrange
            //Act
            //Assert
            Assert.IsNotNull(spball.Answers);
        }

Run the test and as expected we get a failure as Answers is still null. So we’ll fix that by changing the constructor logic in the SharePoint8Ball class. As we add the code below, we know we’re going to need to pass in a parameter for listname frm the webpart, so we’ll include that too.

        public SharePoint8Ball(string listname)
        {
            web = SPContext.Current.Web;
            SPList sourceList = web.Lists[listname];

            SPListItemCollection itemCol = sourceList.Items;

            List<string> answers = new List<string>();

            foreach (SPListItem item in itemCol)
            {
                answers.Add(item.Title);
            }

            base.Answers = answers;

        }

Running the test we hit a compile error as the constructor called in our setup method is now expecting a parameter for the listname.

            spball = new SharePoint8Ball("Listname");

Running the tests now all pass.

At this point, I’m happy that the SharePoint version of our Magic8Ball is now ready to receive items from SharePoint in the next part, I’ll drop it into a Web Part and run it in a SharePoint web page.

Paul

Leave a Reply

Your email address will not be published.

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.