Moq Testing Tutorial

in utopian-io •  7 years ago  (edited)

What Will I Learn?

In this tutorial I will step you through a framework called Moq for (.NET). You will learn creating a base test class and moq testing.

Requirements

  • Moq
  • Moq Contrib
  • Moq API

Difficulty

Intermediate

Tutorial Contents

  • Creating A Base Test Class
  • A Test Class using our Base Class

Moq Testing Tutorial

In this tutorial I will step you through a framework called Moq for (.NET). I will not be discussing what Mocking is here as that’s outside the scope of this tutorial. What is mocking/mock object?

Before we jump in to the code, here are some important links:

Get Moq.

Get Moq Contrib

So lets dive in, assuming you already added a reference to the Moq DLL and Moq.Contrib DLL in your project. Let’s start out by creating a new Abstract class to your project. During this session I am going to call this class: UnitBaseAbstractClass for readability.

Creating A Base Test Class

UnitBaseAbstractClass: This class is in charge of setting up a Moq based environment for each of your TestClass’.

1. First let’s add the Moq and Moq.Contrib namespaces to this UnitBaseAbstractClass class.

using Moq;
using Moq.Contrib;

That was simple!

2. Now lets add the [TestClass] attribute to the top of this class, for anything extending it will now need to be a test.

using Moq;
using Moq.Contrib;

namespace MyUnitTests
{
   [TestClass]
   public abstract class UnitBaseAbstractClass
   {
   }
}

3. This maybe a bit confusing but bare with me; we are now going to add two public members to our UnitBaseAbstractClass class these are of type MockFactory and AutoMockContainer. See the next example:

MockFactory: A utility factory used to manage a mocks lifecycle.

AutoMockContainer: A container used to automatically inject mocks into desired objects.

using Moq;
using Moq.Contrib;

namespace MyUnitTests
{
   [TestClass]
   public abstract class UnitBaseAbstractClass
   {

           public MockFactory _factory;
           public AutoMockContainer _container;

   }
}

4. We will now need to create a method called “Setup” which will take in a MockBehavior enum. In this tutorial we will only use: MockBehavior.Default.

MockBehavior: Tells the MockFactory how to behave. The Strict behavior throws an exception if a mock isn’t setup exactly like its mocked object. Loose will never throw an exception and Default is setup to use the Loose behavior.

(If you don’t understand what this means it’s alright to move on, as it will not affect you yet.)

5. We are going to use Setup to instantiate both MockFactory and AutoMockContainer the factory will need the behavior passed into Setup and the AutoMockContainer will need the MockFactory.Like this:

using Moq;
using Moq.Contrib;

namespace MyUnitTests
{
   [TestClass]
   public abstract class UnitBaseAbstractClass
   {

   public MockFactory _factory;
   public AutoMockContainer _container;

       public void Setup(MockBehavior behavior)
       {
           _factory = new MockFactory(behavior);
           _container = new AutoMockContainer(_factory);
       }

   }
}

6. Last but not least we want to implement our [TestCleanup]. We will call this method VerifyAll, all it will do is verify that all of our mocks were used and that they were used correctly. Making the final code look something like this:

using Moq;
using Moq.Contrib;

namespace MyUnitTests
{
   [TestClass]
   public abstract class UnitBaseAbstractClass
   {

   public MockFactory _factory;
   public AutoMockContainer _container;

       public void Setup(MockBehavior behavior)
       {
           _factory = new MockFactory(behavior);
           _container = new AutoMockContainer(_factory);
       }

       [TestCleanup]
       public void VerifyAll()
       {
           _factory.VerifyAll();
       }

   }
}

Nice, small, and powerful.

A Test Class using our Base Class

The fun part!

1. Let’s start by writing some tests against this pseudo PersonService class:

namespace MyRear
{

   public class PersonService
   {
       private readonly IRepository _repository;

       public PersonService(IRepository repository)
       {
             _repository = repository;
       }
       public bool SavePerson(Person person)
       {
        try{
          return _repository.Save(person);
        }
        catch (Exception ex){
          throw new CustomException(ex);
        }
       }
   }
}

2. Lets create our test class using UnitBaseAbstractClass. Our [TestInitialize] will be using the base Setup method we created in UnitBaseAbstractClass and we will use the AutoMockContainer to create a new instance of our PersonService class. We will also create a new person.That was a mouthful, like this:

namespace MyRearTests
{
   [TestClass]
   public class PersonServiceTests : UnitBaseAbstractClass
   {
       private readonly PersonService _service;
    private Person _person;

       [TestInitialize]
       public void SetupPerson()
       {
           Setup(MockBehavior.Default);
           _service = _container.Create<PersonService>();
        _person = new Person();
   }

   }
}

3. Time to write the tests! Before that though, I want to show you how to mock _repository.Save the code that calls the Database. Which in this case we do not want to access in our testing environment.This code:

return _repository.Save(person);

4. First we want to get a mock object for IRepository because this is how we are going to call Save.

We do this with this line of code:

_container.GetMock<IRepository>()

6. We then want to make sure that we can mock Save. We accomplish this via some Linq we call the Expect method.In it we call Save and pass our _person object.

_container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))

7. We then need to tell the object what to return (in this case I return a bool, lets say true). After calling the Returns method make sure to verify that this mock was even needed via Verifiable method.

_container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))
   .Returns(true)
   .Verifiable();

8. Lets make it a test now!

namespace MyRearTests
{
   [TestClass]
   public class PersonServiceTests : UnitBaseAbstractClass
   {
       private readonly PersonService _service;
   private Person _person;

       [TestInitialize]
       public void PersonServiceSetup()
       {
           Setup(MockBehavior.Default);
           _service = _container.Create<PersonService>();
       _person = new Person();
   }

       [TestMethod]
       public void SavePersonSuccess()
       {
       var result =
               _container.GetMock<IRepository>()
           .Expect(s => s.Save(_person))
               .Returns(true)
           .Verifiable();

           Assert.IsTrue(result, "Person should have saved.");
       }

   }
}

9. Lets add another test that returns false, a failed save.

namespace MyRearTests
{
   [TestClass]
   public class PersonServiceTests : UnitBaseAbstractClass
   {
       private readonly PersonService _service;
   private Person _person;

       [TestInitialize]
       public void PersonServiceSetup()
       {
           Setup(MockBehavior.Default);
           _service = _container.Create<PersonService>();
       _person = new Person();
   }

       [TestMethod]
       public void SavePersonSuccess()
       {
       var result =
           _container.GetMock<IRepository>()
           .Expect(s => s.Save(_person))
           .Returns(true)
           .Verifiable();

           Assert.IsTrue(result, "Person should save.");
       }

       [TestMethod]
       public void SavePersonFail()
       {
       var result =
               _container.GetMock<IRepository>()
           .Expect(s => s.Save(_person))
           .Returns(false)
           .Verifiable();

           Assert.IsFalse(result, "Person should not save.");
       }

   }
}

10. The last test is a bit tricky its why we wrapped Save in PersonService with a try catch and threw a custom exception.

First lets write something like this:

_container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))

11. Handling exceptions is different then normal asserts throwing them is different too.In order to throw an exception we need to call the Throws method instead of returns.

_container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))
   .Throws(new Exception("Save Threw Up"))
   .Verifiable();

12. Lets make sure we handle the code. We do this with the [ExpectedException] attribute on our test methods.

Like so:

       [TestMethod]
       [ExpectedException(typeof(CustomException))]
       public void SavePersonException()
       {
       _container.GetMock<IRepository>()
       .Expect(s => s.Save(_person))
       .Throws(new Exception("Save Threw Up"))
       .Verifiable();
       }

13. The Final Test looks like this:

namespace MyRearTests
{
   [TestClass]
   public class PersonServiceTests : UnitBaseAbstractClass
   {
       private readonly PersonService _service;
   private Person _person;

       [TestInitialize]
       public void PersonServiceSetup()
       {
           Setup(MockBehavior.Default);
           _service = _container.Create<PersonService>();
       _person = new Person();
   }

       [TestMethod]
       public void SavePersonSuccess()
       {
       var result =
           _container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))
   .Returns(true)
   .Verifiable();

           Assert.IsTrue(result, "Person should save.");
       }

       [TestMethod]
       public void SavePersonFail()
       {
       var result =
           _container.GetMock<IRepository>()
   .Expect(s => s.Save(_person))
   .Returns(true)
   .Verifiable();

           Assert.IsFalse(result, "Person should not save.");
       }

       [TestMethod]
       [ExpectedException(typeof(CustomException))]
       public void SavePersonException()
       {
   _container.GetMock<IRepository>()
    .Expect(s => s.Save(_person))
    .Throws(new Exception("Save Threw Up"))
    .Verifiable();
   }
   }
}

I hope this helped you out a bit more in understanding the Moq Framework. 



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Your contribution cannot be approved because it does not follow the Utopian Rules.

Your tutorial is too trivial and can be seen as "copy paste this code" therefore it is rejected.

You can contact us on Discord.
[utopian-moderator]