Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# How to unit test an interface method without implementation

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)

like image 482
Axel Betton Avatar asked Feb 16 '17 12:02

Axel Betton


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Why is C named so?

Because a and b and c , so it's name is C. C came out of Ken Thompson's Unix project at AT&T. He originally wrote Unix in assembly language. He wrote a language in assembly called B that ran on Unix, and was a subset of an existing language called BCPL.


2 Answers

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
}
like image 164
Nkosi Avatar answered Oct 01 '22 23:10

Nkosi


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

like image 36
Harald Coppoolse Avatar answered Oct 01 '22 22:10

Harald Coppoolse