7

I am new to unit testing and to stackoverflow.

I have to test RefreshAmount in the following interface:

public interface IAccountService
{
    double GetAccountAmount(int accountId);
}

And here is a class that depends on this interface:

public class AccountObj
{
    private readonly int _Id;
    private readonly IService _service;
    public AccountObj(int Id, IService service)
    {
        _Id = Id;
        _service = service;
    }
    public double Amount { get; private set; }
    public void RefreshAmount()
    {
        Amount = _service.GetAmount(_Id);
    }
}

How can I unit test the behavior of RefreshAmount?

RefreshAmount calls IService.GetAmount which may make a call to a back-end office but I do not have its implementation. Any suggestion on the way to go would be appreciated. (I have read about moq and dependency injection, but I am quiet new to unit testing)

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Axel Betton
  • 101
  • 1
  • 1
  • 4
  • It feels quite pointless to test an interface itself as there's no implementation to test. 1. Write an implementation and test it with unit test 2. As you mentioned, you can use Moq to provide a test implementation of your interface – J. Tuc Feb 16 '17 at 13:03
  • @J.Tuc I don't think he wants to test the interface but how `AccountObj` uses that interface. – René Vogt Feb 16 '17 at 13:05
  • Your `AccountObj` looks like an entity. You should [not inject dependencies into the constructor of your entities](https://stackoverflow.com/a/28767898/264697). – Steven Feb 16 '17 at 14:04

5 Answers5

8

Using Moq, here is a minimal example test with comments

[TestClass]
public class AccountObjUnitTests {
    [TestMethod]
    public void AccountObj_Given_Id_RefreshAmount_Should_Return_Expected_Amount() {

        //Arrange
        //creating expected values for test
        var expectedId = 1;
        var expectedAmount = 100D;
        //mock implementation of service using Moq
        var serviceMock = new Mock<IService>();
        //Setup expected behavior
        serviceMock
            .Setup(m => m.GetAmount(expectedId))//the expected method called with provided Id
            .Returns(expectedAmount)//If called as expected what result to return
            .Verifiable();//expected service behavior can be verified

        //the system under test
        var sut = new AccountObj(expectedId, serviceMock.Object);

        //Act
        //exercise method under test
        sut.RefreshAmount();


        //Assert

        //verify that expectations have been met
        serviceMock.Verify(); //verify that mocked service behaved as expected
        Assert.AreEqual(expectedAmount, sut.Amount);
    }

    //Samples class and interface to explain example
    public class AccountObj {
        private readonly int _Id;
        private readonly IService _service;
        public AccountObj(int Id, IService service) {
            _Id = Id;
            _service = service;
        }
        public double Amount { get; private set; }
        public void RefreshAmount() {
            Amount = _service.GetAmount(_Id);
        }
    }

    public interface IService {
        double GetAmount(int accountId);
    }
}

And here is a more simplified version of the same test

[TestMethod]
public void AccountInfo_RefreshAmount_Given_Id_Should_Return_Expected_Amount() {
    //Arrange
    //creating expected values for test
    var expectedId = 1;
    var expectedAmount = 100D;
    //mock implementation of service using Moq with expected behavior
    var serviceMock = Mock.Of<IService>(m => m.GetAmount(expectedId) == expectedAmount);
    //the system under test
    var sut = new AccountObj(expectedId, serviceMock);

    //Act
    sut.RefreshAmount();//exercise method under test

    //Assert
    Assert.AreEqual(expectedAmount, sut.Amount);//verify that expectations have been met
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Noksi, this walked me through an example where I was injecting multiple interfaces. It was the perfect working example. Do you find this documentation on Moq https://github.com/Moq/moq4/wiki/Quickstart enough, or are there other resources you can recommend? – Elim Garak Dec 22 '19 at 16:02
  • 1
    @Patrick I usually recommend that linked documentation to give a good grasp of how to use Moq. There are also a lot of examples here on stackoverflow. – Nkosi Dec 22 '19 at 16:21
2

I assume if you have some typing errors in your code, because your interface is called IAccountService while your service implements IService. Your class is called AccountObj while your constructor is called AccountInfo.

So let's assume your service is expected to implement IAccountService and your class is expected to be named AccountInfo.

When writing a unit test to test AccountInfo.RefreshAmount, you'll have to be aware of the requirements of this function; you'll have to know exactly what this function is supposed to do.

Looking at the code it seems that the requirements of RefreshAmount is:

Whatever object that implements IAccountService and whatever Id are used to construct an object of class AccountInfo, the post condition of calling RefreshAmount is that property Amount returns the same value as IAccountService.GetAccountAmount(Id) would have returned.

Or more formal:

  • For any Id
  • For any class that Implements IAccountService
  • The object that is created using Id and IAccountService should after calling RefreshAmount(), return a value for property Amount, equal to the value returned by IAccountService.GetAccountAmount(Id).

Because the requirement says it should work for all Ids and all AccountServices, you can't test your class with all of them. In such cases when writing unit tests you'll have to think of errors that are likely to happen.

In your example it looks that there are no likely errors, but for future versions you could think of that the function would call the service with the incorrect Id, or call the incorrect service, or forgets to save the return value in property Amount, or that property Amount does not return the proper value.

The requirements for your class specify that it should work for every Id and every IAcocuntService. Therefor you are free to provide any Id and AccountService to the constructor of your test object you want to test, that are likely to detect any of the four future errors.

Luckily a Simple AccountInfo class would Suffice

class AccountService : IAccountService
{
    public double GetAccountAmount(int accountId)
    {
        return 137 * accountId - 472;
    }
}

The following unit test tests that the Amount is the amount returned by the provided implementation of IAccountService for several Ids

void TestAccountObj_RefreshAmount() { const int Id = 438; IAccountService accountService = new AccountService(); var testObject = new AccountInfo(Id, accountService);

   testObject.RefreshAmount();
   double amount = testObject.Amount;
   double expectedAmount = accountService.GetAccountAmount(Id);
   Assert.AreEqual(expectedAmount, amount);

}

This test will test all four mentioned likely errors. The only error that it wouldn't find is that it would call the incorrect service if this incorrect service would return exactly the same strange calculated number. That's why I put such a strange calculation in the service, it is very unlikely that any incorrectly called service would return the same error. Especially if you'd test using various TestObject with various Ids

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
1

Unit testing of such sort of things is related to good composition of Your class. Which in this case is. You can pass "the service" to Your class and that should done the work for You.

What You need to do is make "testServiceClass" which will work just for testing, but won't do anything else.

NOTE: code below is missing all the "tests" atributes to brief the code

namespace MyTests
{
    public class AccountObjTest
    {
        public void Test1()
        {
            int testVal = 10;

            AccountObj obj = new AccountObj(testVal, new AccountObjTestService());
            obj.RefreshAmount();        
            Assert.AreEquals(obj.Amount, testVal);
        }
    }

    internal class AccountObjTestService : IAccountService
    {
        public override double GetAmount(int accountId)
        {
            return accountId;
        }
    }
}

It is important for You that the class itself is checked (UNIT-test), not whole implementation of several classes across (INTEGRATION-test).

Tatranskymedved
  • 4,194
  • 3
  • 21
  • 47
  • Exactly. You can't test an interface without an implementation that is injected. You need some sort of implementation, or mock the interface and method. – Sietse Feb 16 '17 at 13:41
1

I strongly suggest that you use Moq for what you are trying to achieve. It will allow you to "Fake" an implementation of any interface that you are passing to the class that you are trying to test. This way you will only test the behaviour of one class at a time. This will make unit testing a whole lot easier in the long run.

Creating a test class seems like an easy option, however, if you are going to deal with a large number of scenarios, that test class will have to grow and become more complicated to cater of all scenarios, and at some point you will have to test that as well because of its complexity.

Try to learn how to use moq, it will pay off in the long run. You will be able to validate data passed to mocked object, control what's being returned by mocked behaviour, test if mocked methods have been called and how many times they have been called. Moq is extremely useful.

Have a look at https://github.com/Moq/moq4/wiki/Quickstart it explains how to use Moq quite well for begginers

J. Tuc
  • 424
  • 2
  • 10
0

I don't know the Moq frameworks. We use MSTest and Microsoft Fakes which can provide you with stubs.

However, a naive straight forward way could be to implement the interface in a test class

public class MyTestImplementation : IAccountService
{
    public bool HasBeenCalled { get; private set; }
    public int ProvidedId { get; private set; }
    public double Amoung { get; set; }

    public double GetAccountAmount(int accountId)
    {
        HasBeenCalled = true;
        ProvidedId = accountId;
    }
}

And your test method:

[TestMethod] // or whatever attribute your test framework uses
public void TestInterface()
{
    const double EXPECTEDAMOUNT = 341;
    const int EXPECTEDID = 42;
    MyTestImplementation testImpl = new MyTestImplementation();
    testImpl.Amount = EXPECTEDAMOUNT;

    var sut = new AccountObj(EXPECTEDID, testImpl);

    sut.RefreshAmount();

    // use your assertion methods here
    Assert.IsTrue(testImpl.HasBeenCalled);
    Assert.AreEqual(EXPECTEDID, testImpl.ProvidedID);
    Assert.AreEqual(EXPECTEDAMOUNT, sut.Amount);

}

If you only want to check that the resulting Amount is correct you can leave out the other properties. Normally you don't want to check how RefreshAmount() does what it should do, but only if the resulting Amount is correct.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • Thanks for the quick answer. So if I understand from your code, the test is about to check if the method has been effectively called and if the provided Id is correct. Right? It is not about checking if the return value is correct. Do I get the concept here? – Axel Betton Feb 16 '17 at 13:09
  • @AxelBetton Oh right, added that verification. I sometimes still get confused _what_ exactly I should really test. – René Vogt Feb 16 '17 at 13:16