Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to properly test an abstract class

I'm currently in the process of creating a unit test for an abstract class, called Component. VS2008 compiled my program no problems so I was able to create a unit test project within the solution. One thing I've noticed, though, is that when the test file has been created, there are these methods which I've never seen before:

internal virtual Component CreateComponent()
        {
            // TODO: Instantiate an appropriate concrete class.
            Component target = null;
            return target;
        }


internal virtual Component_Accessor CreateComponent_Accessor()
        {
            // TODO: Instantiate an appropriate concrete class.
            Component_Accessor target = null;
            return target;
        }

I presume these are for creating a concrete Component class.

Within each Test method, there is this line:

Component target = CreateComponent(); // TODO: Initialize to an appropriate value

how do I initialize this to an appropriate value? Or, how do I instantiate an appropriate concrete class, as stated above by the CreateComponent and the CreateComponent_Accessor methods?

here is the constructor of the abstract class, for additional info:

protected Component(eVtCompId inComponentId, eLayer inLayerId, IF_SystemMessageHandler inMessageHandler)

like image 832
Anthony Avatar asked Mar 04 '13 08:03

Anthony


People also ask

Can you test an abstract class?

The answer is: always test only concrete classes; don't test abstract classes directly . The reason is that abstract classes are implementation details. From the client perspective, it doesn't matter how Student or Professor implement their GetSignature() methods.

How do you spy an abstract class?

The Mockito. spy() method is used to create a spy instance of the abstract class. Step 1: Create an abstract class named Abstract1_class that contains both abstract and non-abstract methods. Step 2: Create a JUnit test case named Abstract1Test.

How do you unit test a class that extends a base class?

If you really need to extend the base class to override something, extract the functionality to a third class and make the extended class a proxy. The extended class does nothing else than call methods on the third class. E.g. This way, you can test the real implementation, without instantiating the base class.

Do abstract classes have to be overriden?

To implement features of an abstract class, we inherit subclasses from it and create objects of the subclass. A subclass must override all abstract methods of an abstract class. However, if the subclass is declared abstract, it's not mandatory to override abstract methods.


2 Answers

You cannot instantiate an abstract class. So you could write a mock implementation of this abstract class (where you should implement the abstract members) in your unit test project and then call the methods you are trying to test. You could have different mock implementations in order to test various methods of your class.

As an alternative to writing a mock implementation you could use a mock framework such as Rhino Mocks, Moq, NSubstitute, ... which could simplify this task and allow you to define expectations for the abstract members of the class.


UPDATE:

As requested in the comments section here's an example.

Let's suppose that you have the following abstract class that you want to unit test:

public abstract class FooBar
{
    public abstract string Foo { get; }

    public string GetTheFoo()
    {
        return "Here's the foo " + Foo;
    }
}

Now in your unit test project you could implement it by writing a derived class implementing the abstract members with mocked values:

public class FooBarMock : FooBar
{
    public override string Foo 
    { 
        get { return "bar" } 
    }
}

and then you could write your unit test against the GetTheFoo method:

// arrange
var sut = new FooBarMock();

// act
var actual = sut.GetTheFoo();

// assert
Assert.AreEqual("Here's the foo bar", actual);

and with a mock framework (Moq in my example) you do not need to implement this abstract class in the unit test but you could directly use the mocking framework to define expectations of the abstract members that the method under test is relying upon:

// arrange
var sut = new Mock<FooBar>();
sut.Setup(x => x.Foo).Returns("bar");

// act
var actual = sut.Object.GetTheFoo();

// assert
Assert.AreEqual("Here's the foo bar", actual);
like image 51
Darin Dimitrov Avatar answered Oct 14 '22 10:10

Darin Dimitrov


This is how I do it. With a inner-nested-class on the UnitTest class.

Please notice the "MyAbstractClass.GetABoolean" method.. to point out how the abstract class can depend upon the implementation of the subclass.

namespace MyCompany.MyProject.UnitTests
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using FluentAssertions;

    [TestClass]
    [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public class MyAbstractClassTests
    {
        [TestMethod]
        public void ConstructorILoggerFactoryIsNullTest()
        {
            Action a = () => new MyUnitTestConcreteClass(null);
            a.Should().Throw<ArgumentNullException>().WithMessage(MyAbstractClass<int>.ErrorMessageILoggerFactoryIsNull);

        }

        [TestMethod]
        public void GetABooleanIsTrueTest()
        {
            /* here is more likely what you want to test..an implemented method on the abstract class */
            Mock<ILoggerFactory> iloggerFactoryMock = this.GetDefaultILoggerFactoryMock();
            MyUnitTestConcreteClass testItem = new MyUnitTestConcreteClass(iloggerFactoryMock.Object);
            Assert.IsTrue(testItem.GetABoolean());
        }   
        
        [TestMethod]
        public void GetSomeIntsIsNotNullTest()
        {
            /* you may not want to test the abstract methods, but you can */
            Mock<ILoggerFactory> iloggerFactoryMock = this.GetDefaultILoggerFactoryMock();
            MyUnitTestConcreteClass testItem = new MyUnitTestConcreteClass(iloggerFactoryMock.Object);
            Assert.IsNotNull(testItem.GetSomeInts());
        }       
        
        
        private Mock<ILoggerFactory> GetDefaultILoggerFactoryMock()
        {
            Mock<ILoggerFactory> returnMock = new Mock<ILoggerFactory>(MockBehavior.Strict);
            ////returnMock.Setup(x => x.SomeBooleanMethod()).Returns(true);
            return returnMock;
        }       
        
        

        internal class MyUnitTestConcreteClass : MyAbstractClass<int>
        {
            internal MyUnitTestConcreteClass(ILoggerFactory loggerFactory) : base(loggerFactory)
            {
            }

            public override ICollection<int> GetSomeInts()
            {
                return new List<int> { 111, 222, 333 };
            }
        }
    }
}

and the "real" abstract class below

public abstract class MyAbstractClass<T> : where T : struct
{

    public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
        
    public WhiteListStepBodyAsyncBase(ILoggerFactory loggerFactory)
    {
        if (null == loggerFactory)
        {
            throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
        }

    }           

    public bool GetABoolean()
    {
          /* note , this is important factor (sometimes), here this implemented method DEPENDS on an abstract method , and why I have the code "return new List<int> { 111, 222, 333 };" above .. see the connection ?? */
                    return this.GetSomeInts().Count > 0;
    }

    public abstract ICollection<int> GetSomeInts();
    

}

This question is older, but the principals are the same.

Here are my VS2019, year 2020 .. package imports.

  <ItemGroup>
    <PackageReference Include="coverlet.msbuild" Version="2.8.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="FluentAssertions" Version="5.10.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="Moq" Version="4.13.1" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
    <PackageReference Include="coverlet.collector" Version="1.0.1" />
  </ItemGroup>  
like image 21
granadaCoder Avatar answered Oct 14 '22 10:10

granadaCoder