0

I want to emphasize that I am fairly new to creating Unit Test but I've been searching far and wide through google and documentation but I can't find a solution or alternative. So currently I am trying to create Unit test for a microservice my team mate and I are working on. The class constructor is structured as follows

 public Constructor(IOptions<AMQ_Config> amqConfig, IConfiguration configuration)
        {
            this.amqConfig = amqConfig.Value;
            this.amqConfig.UserName = configuration["AMQ:UserName"];
            this.amqConfig.Password = configuration["AMQ:Password"];
            //this.errorTypeConfig = errorTypeConfig.Value;
            this.configuration = configuration;
            AMQSubscriber();
        }

When I create a new instance of the constructor in the Unit Test it will always call and iterate through the AMQSubscriber(); method. Naively I just made a duplicate constructor that excludes the method and adds another parameter:

public UnitTestConstructor(IOptions<AMQ_Config> amqConfig, IConfiguration configuration, IConnection connection)
        {
            this.amqConfig = amqConfig.Value;
            this.amqConfig.UserName = configuration["AMQ:UserName"];
            this.amqConfig.Password = configuration["AMQ:Password"];
            //this.errorTypeConfig = errorTypeConfig.Value;
            this.configuration = configuration;
            this.connection = connection;
        }

but this was done for unit testing purposes only. I've read around about how its not a good idea to do this because it defeats the purpose of Unit testing but I can't think how to isolate this since most of the methods require or depend on the parameters: IOptions<AMQ_Config> amqConfig, IConfiguration configuration and our microservice is architectured for Apache NMS AMQ for sending, processing and receiving messages.

B.Allen
  • 79
  • 1
  • 7
  • 1
    What does `AMQSubscriber` do and why do you not want to call it from your tests? – Lee Mar 18 '20 at 23:03
  • One way is to use a preprocessor directive : https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/. – jdweng Mar 18 '20 at 23:03
  • 2
    First of all, try not to do work in your constructor. Next, unit tests are a great indicator for code that is smelly or suboptimal - which is what you've discovered. If `AMQSubscriber()` is registering or interacting with a message queue then you need to look for a way for that to be mocked out (replace the real thing with your mock). – slugster Mar 18 '20 at 23:09
  • @slugster could you provide an example but your saying in the unit test class I need to generate a mock of the constructor which doesn't include the AMQ subscriber method being called right? – B.Allen Mar 18 '20 at 23:14
  • @Lee AMQSubscriber is the brains of the microservice which both setups up the connection with the AMQ Broker and processes the messages on the Queues – B.Allen Mar 18 '20 at 23:20
  • @B.Allen No, don't mock the ctor, mock the message queue. So `AMQSubscriber()` can run, but it runs against a mock object that either does nothing or simply returns canned data/responses. This is too big a subject to give a simple example for - look at using a mocking framework. – slugster Mar 19 '20 at 01:09
  • @B.Allen There are design issues here that are just being revealed by the unit testing problem. However there is not enough context provided to be able to advise you on those design changes. – Nkosi Mar 19 '20 at 01:15

1 Answers1

1

You have a few options:

1. Move AMQSubscriber() to another interface

So you will have your constructor look like:

public Constructor(IOptions<AMQ_Config> amqConfig, IConfiguration configuration, IAMQSubscriber subscriber)
{
    // Other code ...
    subscriber.AMQSubscriber();
}

In your unit test, you could either mock IAMQSubscriber using a mocking library, or you could provide a void implementation:

class VoidAMQSubscriberForUnitTest : IAMQSubscriber
{
    public void AMQSubscriber()
    { 
        // Do nothing.
    }
}

For example:

// Real
new YourClass(... , new RealAMQSubscriber());
// Unit test
new YourClass(... , new VoidAMQSubscriberForUnitTest()); 

2. Make internal constructor or static methods:

In this case, only types in the same assembly and the unit test assembly could see those methods. i.e. stop test specific methods being seen publicly.

class YourClass
{
    // Only consumed by unit test
    internal YourClass(IOptions<AMQ_Config> amqConfig, IConfiguration configuration, IConnection connection) 
    { }

    // Only consumed by unit test
    internal static YourClass CreateForUnitTest() { }
}

You might need to add InternalsVisibleToAttribute to your assembly.

Community
  • 1
  • 1
weichch
  • 9,306
  • 1
  • 13
  • 25
  • I was with you up till I saw the second suggestion. – Nkosi Mar 19 '20 at 01:12
  • @Nkosi Haha, just give some more suggestions people can choose from really. People prefer different solutions :) – weichch Mar 19 '20 at 01:21
  • I know what you mean. – Nkosi Mar 19 '20 at 01:22
  • @weichch both are solid but the first suggestion may not work since it will cause a cascade effect to the entire code base but the second option might be feasible. I'll have to look more into the details of doing that. – B.Allen Mar 19 '20 at 13:43