0

Lets say I have a method A

public class ClassWithAMethod
{
   public static void A(string email)
   {
      var b = SomeClass.StaticMethod();
      var c = b.GetUser(x => x.Email == email);
   
      if (c != null)
      {
          throw new Exception();
      }
   }
}


And I need to test a method Abc, which calls method A. For example

public void Abc()
{
   ClassWithAMethod.A("123@gmail.com");
}

So I need to mock object b and setup method GetUser but how can I do that?

Roomey
  • 716
  • 2
  • 9
  • 16
  • 2
    That's not possible without modifications to the code. You can only Mock virtual instance methods (or methods from an interface) – PMF Feb 22 '21 at 19:23

2 Answers2

3

Without modification, i don't think this is possible.

a slight modification and non breaking change could be to add an optional param bGetter to get the b. You can then create your own bGetter in your unit test.

public class ClassWithAMethod
{
   public static void A(string email, Func<B> bGetter = null)
   {
      var b = null == bGetter ? SomeClass.StaticMethod() : bGetter();
      var c = b.GetUser(x => x.Email == email);
   
      if (c != null)
      {
          throw new Exception();
      }
   }
}

Isitar
  • 1,286
  • 12
  • 29
  • Nothing prevents you to call this method in the following way: `ClassWithAMethod.A("email@address.com", () => (B)null);` – Peter Csala Feb 23 '21 at 08:54
  • 1
    @PeterCsala while you're right, nothing prevents SomeClass.StaticMethod to return null aswell. The same applies for your factory answer, your factory can return null aswell. Basicly your and my answer are the same, yours is with constructor-injection and mine with method/function injection. – Isitar Feb 23 '21 at 11:56
  • Yes exactly. I've just wanted to point out that by allowing to inject arbitrary user code we have to pay extra attention when we are working with that. – Peter Csala Feb 23 '21 at 12:04
1

As the adage goes: You can solve every problem with another level of indirection, except for the problem of too many levels of indirection Reference

By introducing a wrapper / adapter your solution become a bit more loosely coupled.

Contract

So, first let's introduce the following interface:

public interface IBFactory
{
  B Create();
}

Wrapper

Let's create the wrapper:

public class BWrapper: IBFactory
{
  public B Create()
  {
    return SomeClass.StaticMethod();
  }
}

Alternative

In case of C# 8 you can combine the previous two steps into one by relying on the default implementation in interface feature:

public interface IBFactory
{
  public B Create()
  {
    return SomeClass.StaticMethod();
  }
}

Relying on abstraction

Now let's modify the ClassWithAMethod class to rely on the IBFactory:

public class ClassWithAMethod
{
   private readonly IBFactory _bFactory;
   public ClassWithAMethod(IBFactory bFactory)
   {
     this._bFactory = bFactory;
   }
   
   public static void A(string email)
   {
      var b = this._bFactory.Create();
      var c = b.GetUser(x => x.Email == email);
   
      if (c != null)
      {
          throw new Exception();
      }
   }
}

You can register the BWrapper as the default implementation for IBFactory in your DI container. Or if you are not using DI nor C# 8 then you can specify a parameterless ctor where you rely on BWrapper. Please bear in mind that this approach is not advised. It is more like a last resort.

public class ClassWithAMethod
{
   private readonly IBFactory _bFactory;

   public ClassWithAMethod()
   {
     this._bFactory = new BWrapper();
   }

   public ClassWithAMethod(IBFactory bFactory)
   {
     this._bFactory = bFactory;
   }

   ...
}

Unit testing

Now you can mock that dependency as well.

var bFactoryMock = new Mock<IBFactory>();
bFactoryMock.Setup(factory => factory.Create()).Returns(...);

var SUT = new ClassWithAMethod(bFactoryMock.Object);
Peter Csala
  • 17,736
  • 16
  • 35
  • 75