1

I am trying to understand principle of Unit testing, Purpose, and Moq. We have complicated Service Call.

The Service Call encompasses numerous Repositories and Services, with dependencies and parameters. We are mocking each method per below. By the time we are done mocking, it seems like people are faking whole test, to product an expected result, which we will receive.

Many articles regarding this discuss this issue.

I guess the question is what is best way to test this and general guidelines? If we want to test expected behavior, its as though we are forging it with moq.

Our team has already conducted unit testing for the underliny repository and services below. Lets say the purpose of the Service Call is to calculate car insurance cost. Other repositories services, find out risks, population group, factors, etc

public async Task ServiceCall(int document, Product product)
{
     await _servicecall.MethodA;
     await _servicecall.MethodB;
     await _servicecall.MethodC;
     if...etc
     await _repository.MethodD;
     await _repository.MethodE;
     await _servicecall.MethodF;
     await _repository.MethodG;
     await _servicecall.MethodF;
}

Moq

for each repository and service within

 mock.Setup(b => b.ServiceCallB().ReturnsAsync(etc);

Articles:

https://dev.to/asizikov/you-are-mocking-it-wrong-5gh3

https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a

2 Answers 2

2

There is nothing here to test.

The point of a test is to help you read code. That is, to understand what it does and, more importantly, what it was meant to do. The only thing shown here is a bunch of things that need to be called. That's trivially obvious and tells us nothing about intent.

If the method had a good name or even a comment that made the intent of this code clear a test could be created against that idea but there isn't one here.

You should test behavior. Interesting business logic that enforces policy. Things that are hard to read in code. You set up tests that don't care HOW this is done. They care THAT it's done. Those tests will let you do what you need done a bunch of different ways and will tell you if you've still done them after you've changed things around. That's called refactoring. Tests should make refactoring easy.

Any test written against code like this, that only cares that methods were called, would cast your implementation in stone. If that's all you want then use source control.

A great way to stop thinking about tests this way it to write the test before the implementation exists. Now you can focus on what you need and not on how it's done. The test isn't for the code under test. It's for all the other code that needs the code under test to do its job.

Given all this I hope you can see why I can't create a test from your example code. There is nothing here telling me what ServiceCall is meant to do. Without knowing your intent I can't write a test that checks if it was accomplished. What methods or classes ServiceCall used to accomplish its goal is not any of the tests business.

It's easy to let isolation concerns move you away from testing what the code under test does to how it does it. Understand that a unit doesn't have to be a method or a class. it's blob of code that gets something specific done. Hopefully without involving IO like talking to the database, network, or file system. The blob doesn't have to be small. It has to be fast and deterministic. You should be able to control everything that will make it work. And you shouldn't care about how it works.

A good test states a need extremely formly. So formal that it compiles. With the system in state x you put y in the blob and the result is z. How that happened is not the tests concern.

Given that please don't use mocks to carve up the blob in every possible way. Isolation is good but not at every single possible boundary. Write tests that help me find the problem when they fail. Not tests that force me to solve the problem a certain way.

The best boundary to test at is a conceptual one. One that makes the tests expectations easy to understand. This is much more about what happens in our heads then the structure of the code.

2
  • just added a comment, lets say purpose of service call is to calculate insurance premium cost, the repositories and other services, find out factors, risk, etc,
    – GregDavis
    Commented Sep 19, 2019 at 15:33
  • To be slightly fair, the if logic in the method (which OP omitted) can be worth unit testing (checking if certain methods are(n't) called under certain conditions). But this answer is otherwise correct, the straightforward calls don't need to be tested.
    – Flater
    Commented Sep 19, 2019 at 16:01
0

I guess the question is what is best way to test this and general guidelines?

Growing Object Oriented Software, Guided By Tests, by Pryce and Freeman, is an important work in the space, so you should familiarize yourself with that material.

The Service Call encompasses numerous Repositories and Services, with dependencies and parameters.

If you have to run a test with seven, eight, ten external dependencies, because reasons? then you have to -- and it is painful, and it sucks.

The TDD answer is that the pain is telling you to rethink your design - replace this implementation with an alternative that is easier to test and reason about.

One possibility is to look for higher level abstractions that can encapsulate some of your existing calls to the repository and the service. You might imagine that it could look something like

public async Task ServiceCall(int document, Product product)
{
     await _before.MethodA;
     if...etc
     await _after.MethodB;
}

Now the test of this component only needs two mocks, and the rest of the testing work is delegated to two other, simpler tests.

More generally, we're taking the coarse grained ServiceCall and decomposing it into a graph of different ideas that can be isolated from one another, which makes the testing of any one of those ideas much easier to manage.

You'll find this kind of an approach described in GOOS, or by Justin Searls. Note that there is a trade-off, you get isolation of different ideas, but you no longer have everything in the local context.

A different design that can be effective is to separate the responsibility for deciding what to do from the orchestration of actually doing it. Cory Benfield's talk on protocol libraries is a good introduction to this idea.

The basic shape that we have a finite state machine that holds all of the logic for deciding what to do, but no dependencies on the mocks. Then we have an orchestration component that knows how to ask the FSM what to do, and tell the FSM what happened. The orchestration component is really easy to test, because you can use a mock fsm to tell it to do one specific thing; thus, most of your tests of the orchestration is done with an fsm mock and a mock side effect, with all of the other "dependencies" just replaced by null objects.

while (! fsm.isDone ) {
    switch fsm.next:
        case A:
            await _servicecall.MethodA
            fsm.onMethodA
        case B:
            await _servicecall.MethodB
            fsm.onMethodB
        // ...
    }
}

It's another trade - you get simple test isolation and separation of concerns, but you give away some of the ability to just read the list of effects in sequence.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.