2

I have an abstract class Shape and a couple of classes inheriting from it (Square, Rectangle, Triangle, Circle, ...). I also have an AreaCalculator class, which contains a method named CalculateAreasByShape(). This method calls a series of CalculateShapeAreas<TShape>(List<TShape> shapes) methods for each Shape type.

Now, in my unit tests, I have the following code:

...
var areaMock = new Mock<AreaCalculator>(MockBehavior.Strict);
areaMock.Setup(m => m.CalculateAreasByShape()).Returns(new Dictionary<Type, decimal>());
var canvas = new DemoCanvas(shapes, perimeterMock.Object, areaMock.object);
var result = canvas.RunShapeCalculations();
...

This code fails on the fact, that MockBehavior is set to Strict and there is no setup for the CalculateShapeAreas<TShape>() methods. Now, I know I could run a setup for each of the Shape types, e.g.:

areaMock.Setup(m => m.CalculateShapeAreas<Triangle>(It.IsAny<List<Triangle>>())).Returns(default(decimal));

But this will create a lot of duplicate code. I wonder whether we can use the fact that all shapes inherit from the same base class. I have tried the following, but it does not work:

areaMock.Setup(m => m.CalculateShapeAreas<Shape>(It.IsAny<List<Shape>>())).Returns(default(decimal));

Alternatively, is it possible to set MockBehavior.Loose on a single generic method (meaning, it would not check the actual type)?

Storm
  • 3,062
  • 4
  • 23
  • 54
  • 1
    Why don't you mock the Interface instead of the class? Like; `new Mock`? – ilkerkaran Jul 25 '19 at 07:25
  • Because later in the test I am also asserting number of calls of different methods within the `AreaCalculator`, which call each other. I skipped some of the setups in order to avoid unnecessary code in my question. – Storm Jul 25 '19 at 07:35
  • If this is a Unit test (and not an integration test) and its purpose is to test `canvas.RunShapeCalculations()` then you can mock every combination via Interface. This unit test does not need to know anything going on in `CalculateAreasByShape`, it is only related to the result that you can mock as you like. Eventhough, in `CalculateAreasByShape` unit test methods, all of the generic methods gonna need to be mocked – ilkerkaran Jul 25 '19 at 07:38
  • As I mentioned, I have presented only snippets from the actual code. The whole setup of mocks is in a separate `Arrange` method, which is called in individual unit tests. Some check exceptions, others method results, etc. There are tests also for the `CalculateShapeAreas` method. I just wanted to find a shorter way to mock a generic method for each type which is tested. If it is not possible, please add an answer that it is not possible. – Storm Jul 25 '19 at 07:46
  • 1
    AFAIK, you need to get rid of `MockBehavior.Strict` to be able to do that and then the class should be marked as virtual. I have had concerned about the simplicity of your codebase that's why asked for an Interface. Happy coding. – ilkerkaran Jul 25 '19 at 07:57
  • Allright, thank you. I wanted to avoid possible obscure test results with `MockBehavior.Strict`, but I guess I will have to decide whether it is really necessary. – Storm Jul 25 '19 at 08:00
  • Could you in your interface use the `IEnumerable` instead of the `List`? – Johnny Jul 25 '19 at 08:51
  • 1
    Similar to the problem described in [Mocking generic methods in Moq without specifying T](https://stackoverflow.com/questions/20072429/). Just like Moq has the `It.IsAny<>` magic for usual parameters, it would be nice if it has `Moq.AnyType` for type parameters. Then you could do, `mock.Setup(pa => pa.Reserve()).Returns(...)`, for example. And in your example, `areaMock.Setup(m => m.CalculateShapeAreas(It.IsAny>())).Returns(default(decimal));` where in fact the first `Moq.AnyType` could be inferred. But what if the type needs _constraints_? – Jeppe Stig Nielsen Jul 25 '19 at 09:41
  • 2
    @JeppeStigNielsen it will be eventually https://github.com/moq/moq4/issues/343 – Johnny Jul 25 '19 at 09:44

1 Answers1

2

It.IsAny for open generic support is not yet present in the moq(release 4.12.0). Eventually there are some plans to introduce this, take a look here


I think problem you are facing is because you are using List<T> as an input parameter. As you might notice List<T> is not covariant.

What that mean?

List<Shape> shapeList;
IEnumerable<Shape> shapeEnumerable;
var triag = new List<Triangle>();

shapeList = triag; // NOT ALLOWED
shapeEnumerable = triag; // ALLOWED

The same makes problem while mocking. One solution could be to change interface and instead of passing List<T> pass IEnumerable<out T>, or as @JeppeStigNielsen pointed out IReadOnlyList<out T>. In fact any covariant in T interface which is implemented by List<T> could be used.

areaMock
    .Setup(m => m.CalculateShapeAreas<Shape>(It.IsAny<IEnumerable<Shape>>()))
    .Returns(default(decimal));
Johnny
  • 8,939
  • 2
  • 28
  • 33
  • 1
    If needed, you can also use `IReadOnlyList` which is also covariant in `T`. Both a `T[]` and a `List` implement it. With that, you can use stuff like the `shapes.Count` property and the `get` part of the indexer, `shapes[i]`. – Jeppe Stig Nielsen Jul 25 '19 at 09:54
  • @JeppeStigNielsen indeed, tnx – Johnny Jul 25 '19 at 09:56
  • In this case, your link to https://github.com/moq/moq4/issues/343 is the correct answer for me. Please, could you add it to your post, as well? – Storm Jul 25 '19 at 10:38