Unit Test a method that returns a void

29,465

Solution 1

Since your method returns void, it probably has some side-effect that you can test/assert on.

In your case, an option would be to provide a mock instance of IDeviceAuthorisationRepositioryService. You can then check if a call to UpdatePhoneStatusToActive has happened. Here is a solution using Moq:

var mock = new Mock<IDeviceAuthorisationRepositioryService>();

var service = new DeviceAuthorisationService(mock.Object);
service.UpdateDeviceStatusToActive(....);

mock.Verify(x => service.UpdatePhoneStatusToActive(), Times.Never());

Solution 2

If a method is void, then it should have some observable side-effect - otherwise it's pointless. So instead of testing the return value, you test the side-effects. In this case, it looks like those a probably around which exceptions are thrown in which situations.

(Here, "throws an exception" is deemed a side-effect; you could also think of it as an implicit kind of return value of course...)

Solution 3

Inject a mocked repository. Test if certain methods on the repository are called.

Solution 4

You can set exception expectancies on your unit tests. In nUnit it looks like this:

[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void TestAuthoriseFail()
{
    // do something that should make the tested method throw the exception
}

Solution 5

Even if your method returns void, it must be doing something that is useful for you (otherwise it would be a pointless method to have).

From your code, I'm guessing there are essentially 2 flavours of 'useful' things that the AuthoriseDeviceProfile method is doing:

  • calling the GetSelectedPhoneDetails method on the IDeviceAuthorisationRepositiory
  • throwing various exceptions based on certain conditions

Therefore to unit test the method, you should do two things that correspond to this:

  • Inject a mock IDeviceAuthorisationRepositiory and have it record and/or assert whether GetSelectedPhoneDetails is called
  • Exercise test methods that induce the various exceptions, and capture them as they are thrown to verify that:
    • an exception is in fact thrown
    • the exception that is thrown is the appropriate one for each scenario
Share:
29,465
Venu b
Author by

Venu b

Updated on May 15, 2020

Comments

  • Venu b
    Venu b almost 4 years

    Wanted to Unit Test a method in the following Class

    public class DeviceAuthorisationService : IDeviceAuthorisationService
    {
        private DeviceDetailsDTO deviceDetailsDTO = null;
        private IDeviceAuthorisationRepositiory deviceAuthorisationRepositiory;
    
        public DeviceAuthorisationService(IDeviceAuthorisationRepositioryService paramDeviceAuthorisationRepository)
        {
            deviceAuthorisationRepositiory = paramDeviceAuthorisationRepository;
        }
    
        public void AuthoriseDeviceProfile(long paramUserID, string paramClientMakeModel)
        {
            if (deviceDetailsDTO == null)
                GetCellPhoneDetails(userID);
    
            if (deviceDetailsDTO.IsDeviceSelected == false)
                throw new SomeCustomExceptionA();
    
            if (deviceDetailsDTO.CellPhoneMakeModel.ToLower() != paramClientMakeModel.ToLower())
                throw new SomeCustomExceptionB;
        }
    
        public void UpdateDeviceStatusToActive(long userID)
        {
            if (deviceDetailsDTO == null)
                throw new InvalidOperationException("UnAuthorised Device Profile Found Exception");
    
            if (deviceDetailsDTO.PhoneStatus != (short)Status.Active.GetHashCode())
                deviceAuthorisationRepositiory.UpdatePhoneStatusToActive(deviceDetailsDTO.DeviceID);
        }
    
        private void GetCellPhoneDetails(long userID)
        {
            deviceDetailsDTO = deviceAuthorisationRepositiory.GetSelectedPhoneDetails(userID);
    
            if (deviceDetailsDTO == null)
                throw new SomeCustomException()
        }
    
    }
    

    Note:

    • Method Name = AuthoriseDeviceProfile returns void
    • The method checks userSentMakeModel against the one stored in the db match
    • If it matches - it simply returns (ie does not change any state)

    How will we unit test this method?

    • Have mocked the Repo
    • Have covered scenario of "THROWS EXCEPTION"
    • Question is how to unit test the scenario of ALL WENT WELL ie user;s makeModel matched with repository;s makeModel

    Any design suggestions to make this testable is most welcome Thanks in advance.

  • Venu b
    Venu b over 11 years
    Thanks Chamila_c. Have covered the "throws Exception" in the unit test by sending in a mock repository. How do we cover the "all went well scenario"
  • Chamila Chulatunga
    Chamila Chulatunga over 11 years
    Set up the test scenario so that you don't expect an exception to be thrown, and then check that you aren't picking up any exceptions in the 'catch' clause. This can be as simple as setting a boolean value up front, and only having code to toggle it in the 'catch'. Then if at the end of the test the boolean has retained its initial value, you know that no exceptions were thrown
  • Venu b
    Venu b over 11 years
    Isnt try catch inside a test considered as a test smell?
  • Chamila Chulatunga
    Chamila Chulatunga over 11 years
    Generally, yes, try-catch in tests is a bad idea, and certainly there are more sophisticated ways of testing for exceptions (e.g see Phil Gan's answer). However if you aren't able to use the more sophisticated mechanisms, are specifically testing for exceptions, and are careful to only catch the type of exceptions you are testing for, then I think it's an ok way to go.
  • Venu b
    Venu b over 11 years
    Thanks alexn - Have been using Moq and finding it very helpful
  • Rob McCabe
    Rob McCabe over 5 years
    What if there's no interface for the injected class AND what if it's a sealed class... what can you do in this case?