0

Writing tests for CRUD controllers which follow this pattern.

  1. Get one or more parameters of various types from action method parameters
  2. Call some IEntityService.GetEntity(parameters from step 1) method
  3. If it returns null return NotFound
  4. Otherwise return the found object

I found myself writing very similar tests repeatedly.

        [TestCase(true)]
        [TestCase(false)]
        public void GetsAccount(bool isExistingAccount)
        {
            const int accountId = -1;
            var account = isExistingAccount ? new Account() : null;
            A.CallTo(() => AccountService.GetAccount(accountId)).Returns(account);

            var result = (IStatusCodeActionResult)_controller.GetAccount(accountId);

            var statusCode = isExistingAccount ? HttpStatusCode.OK : HttpStatusCode.NotFound;
            result.StatusCode.Should().Be((int)statusCode);

I attempted to write a generic method to fake.

        public void GetEntityActionMethodTest<TEntityType>(bool isExisting, Func<int, TEntityType> serviceMethod, Func<int, ActionResult> actionMethod) where TEntityType : class
        {
            var fakeMethod = A.Fake<Func<int, TEntityType>>();
            A.CallTo(() => fakeMethod(-1)).Returns( isExisting ? default(TEntityType) : null);
            var result = (IStatusCodeActionResult)actionMethod(-1);
            result.StatusCode.Should().Be(isExisting ? (int)HttpStatusCode.OK : (int)HttpStatusCode.NotFound);
        }

There are two issues with it:

1) Does not fake correctly to return null

2) Assumes interface method has one integer parameter

Questions

1) Is it a good idea to work on creating a generic method that can fake methods that may have different signatures in different interfaces using FakeItEasy? How about with reflection?

2) If yes, how can I do that?

Thanks.

Alper
  • 663
  • 1
  • 7
  • 24
  • Do you need to use FakeItEasy? Are you content with just finding a solution? I believe Moq is better and more documented. – Narendran Pandian May 16 '19 at 15:27
  • Yes, working on an existing project. Switching to Moq is not an option. – Alper May 16 '19 at 15:32
  • btw I am very much interested in this answer as well. I find myself faking a lot of the same methods as well. – Narendran Pandian May 16 '19 at 15:35
  • I am not anywhere near an expert unit tester. So in my opinion, writing a generic setup for the same interface is fine since, if you have the same interface, you usually have the same behaviour and therefore use the same mocks. Different interfaces with different types? Usually that requires a different behaviour ( not talking about just type here), even if it does not, you are making your unit tests dependent on one mock a bit much. – Narendran Pandian May 16 '19 at 15:43
  • *Is it a good idea to work on creating a generic method that can fake methods that may have different signatures in different interfaces* - no I do not think so. The interface you are mocking can expose any method or operation signature with any return type. It may also be desirable to vary the composition of the return type based on what you are looking to test. Your tests at the moment are very narrow in that they are testing http response code. But what about the return types? Don't you care about them? What about http response headers etc? – tom redfern May 16 '19 at 15:44
  • Depending on the action method I'm testing I do care about them. I plan to check to those in the specific unit tests. My objective is to avoid mocking and then asserting very similar thing repeatedly. – Alper May 16 '19 at 16:01

1 Answers1

2

I'm not entirely sure what your goal is. You say you're trying to write a generic method to fake, but your sample looked like the complete test. I'll talk about how to make the fake. Incorporating it into the tests should be straightfoward.

If the goal is to create a fake that will return a particular object when any method is called, parameterized by the fake type and the return value, you could use the FakeItEasy.Sdk namespace methods to create the object and you can configure the fake to respond to any method like so:

public object CreateFakeWithReturnValue(Type typeOfFake, object returnValue)
{
    var fake = Create.Fake(typeOfFake);
    A.CallTo(fake).WithNonVoidReturnType().Returns(returnValue);
    return fake;
}

Of course, this method could be made more sophisticated by looking at the call that's passed in (or if you know the type of the fake, you could make this method a generic and just use the standard A.Fake construction).

As @tom redfern says, this may not be the best path in all cases, but you'll have to judge whether the overall approach makes sense to you. As tests get more sophisticated, you may find yourself augmenting the method quite a lot, to the point where it just makes more sense to go back to hand-crafted fakes.

Blair Conrad
  • 233,004
  • 25
  • 132
  • 111
  • Thanks for your post. The goal: Determine what it takes to avoid writing many test similar to the one in the question (simply replace Account with Employee/Customer etc.) – Alper May 16 '19 at 16:05
  • This answer was very helpful in getting to a solution. If you find yourself trying to solve a similar problem, it may be useful to step back and re-evalute the why. In my case, I did not have a good answer to the question: why unit test nearly identical action methods? Consider @tom redfern's comment above. – Alper May 17 '19 at 14:26