Martin
Martin

Reputation: 24308

TDD: .NET following TDD principles, Mock / Not to Mock?

I am trying to following TDD and I have come across a small issue. I wrote a Test to insert a new user into a database. The Insert new user is called on the MyService class, so I went ahead and created mytest. It failed and I started to implement my CreateUser method on my MyService Class.

The problem I am coming across is the MyService will call to a repository (another class) to do the database insertion.

So I figured I would use a mocking framework to mock out this Repository class, but is this the correct way to go?

This would mean I would have to change my test to actually create a mock for my User Repository. But is this recommended? I wrote my test initially and made it fail and now I realize I need a repository and need to mock it out, so I am having to change my test to cater for the mocked object. Smells a bit?

I would love some feedback here.

If this is the way to go then when would I create the actual User Repository? Would this need its own test?

Or should I just forget about mocking anything? But then this would be classed as an integration test rather than a unit test, as I would be testing the MyService and User Repository together as one unit.

I a little lost; I want to start out the correct way.

Upvotes: 4

Views: 798

Answers (5)

Liberty Lover
Liberty Lover

Reputation: 992

Summary

I am a huge fan of Eiffel, but while the tools of Eiffel like Design-by-Contract can help significantly with the Mock-or-not-to-Mock question, the answer to the question has a huge management-decision component to it.

Detail

So—this is me thinking out loud as I ponder a common question. When contemplating TDD, there is a lot of twisting and turning on the matter of mock objects.

To Mock or Not to Mock

Is that the only binary question? Is it not more nuanced than that? Can mocks be approached with a strategy?

If your routine call on an object under test needs only base-types (i.e. STRING, BOOLEAN, REAL, INTEGER, etcetera) then you don't need a mock object anyhow. So, don't be worried.

If your routine call on an object under test either has arguments or attributes that require mock objects to be created before testing can begin then—that is where the trouble begins, right?

What sources do we have for constructing mocks?

Simple creation with:

  • make or default create
  • make with hand-coded base-type arguments Complex creation with:
  • make with database-supplied arguments
  • make with other mock objects (start this process again) Object factories
  • Production code based factories
  • Test code based factories
  • Data-repo based data (vs hand-coded) Gleaned
  • Objects from prior bugs/errors

THE CHALLENGE:

Keeping the non-production test-code bloat to a bare minimum. I think this means asking hard but relevant questions before willy-nilly code writing begins.

Our optimal goal is:

  • No mocks needed. Strive for this above all.
  • Simple mock creation with no arguments.
  • Simple mock creation with base-type arguments.
  • Simple mock creation with DB-repo sourced base-type arguments.
  • Complex mock creation using production code object factories.
  • Complex mock creation using test-code object factories.
  • Objects with captured states from prior bugs/errors.

Each of these presents a challenge. As stated—one of the primary goals is to always keep the test code as small as possible and reuse production code as much as possible.

Moreover—perhaps there is a good rule of thumb: Do not write a test when you can write a contract. You might be able to side-step the need to write a mock if you just write good solid contract coverage!

EXAMPLE:

At the following link you will find both an object class and a related test class:

Class: https://github.com/ljr1981/stack_overflow_answers/blob/main/src/so_17302338/so_17302338.e Test: https://github.com/ljr1981/stack_overflow_answers/blob/main/testing/so_17302338/so_17302338_test_set.e

If you start by looking at the test code, the first thing to note is how simple the tests are. All I am really doing is spinning up an instance of the class as an object. There are no "test assertions" because all of the "testing" is handled by DbC contracts in the class code. Pay special attention to the class invariant. The class invariant is either impossible with common TDD facilities, or nearly impossible. This includes the "implies" Boolean keyword as well.

Now—look at the class code. Notice first that Eiffel has the capacity to define multiple creation procedures (i.e. "init") without the need for a traffic-cop switch or pattern-recognition on creation arguments. The names of the creation procedures tell the appropriate story of what each creation procedure does.

Each creation procedure also contains its own preconditions and post-conditions to help cement code-correctness without resorting to "writing-the-bloody-test-first" nonsense.

Conclusion

Mock code that is test-code and not production-code is what will get you into trouble if you get too much of it. The facility of Design-by-Contract allows you to greatly minimize the need for mocks and test code. Yes—in Eiffel you will still write test code, but because of how the language-spec, compiler, IDE, and test facilities work, you will end up writing less of it—if you use it thoughtfully and with some smarts!

Upvotes: 0

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236188

So I figured I would use a mocking framework to mock out this Repository class, but is this the correct way to go?

Yes, this is a completely correct way to go, because you should test your classes in isolation. I.e. by mocking all dependencies. Otherwise you can't tell whether your class fails or some of its dependencies.

I wrote my test initially and made it fail and now I realize I need a repository and need to mock it out, so I am having to change my test to cater for the mocked object. Smells a bit?

Extracting classes, reorganizing methods, etc is a refactoring. And tests are here to help you with refactoring, to remove fear of change. It's completely normal to change your tests if implementation changes. I believe you didn't think that you could create perfect code from your first try and never change it again?

If this is the way to go then when would I create the actual User Repository? Would this need its own test?

You will create a real repository in your application. And you can write tests for this repository (i.e. check if it correctly calls the underlying data access provider, which should be mocked). But such tests usually are very time-consuming and brittle. So, it's better to write some acceptance tests, which exercise the whole application with real repositories.

Or should I just forget about mocking anything?

Just the opposite - you should use mocks to test classes in isolation. If mocking requires lots of work (data access, ui) then don't mock such resources and use real objects in integration or acceptance tests.

Upvotes: 6

Rogério
Rogério

Reputation: 16380

Your initial test was incomplete, that's all. The final test is always going to have to deal with the fact the new user gets persisted.

TDD does not prescribe the kind of test you should create. You have to choose beforehand if it's going to be a unit test or some kind of integration test. If it's a unit test, then the use of mocking is practically inevitable (except when the tested unit has no dependencies to isolate from). If it's an integration test, then actual database access (in this case) would have to be taken into account in the test.

Either kind of test is correct. Common wisdom is that a larger unit test suite is created, testing units in isolation, while a separate but smaller test suite exercises whole use case scenarios.

Upvotes: 0

user1540857
user1540857

Reputation: 194

I have two set of testing libraries. One for UnitTests where I mock stuff. I only test units there. So if I would have a method of AddUser in the service I would create all the mocks I need to be able to test the code in that specific method. This gives me a possibility to test some code paths that I would not be able to verify otherwise.

Another test library is for Integration tests or functional tests or whatever you want to call it. This one is making sure that a specific use case. E.g. Creating a tag from the webpage will do what i expect it to do. For this I use the sql server that shipps with Visual studio 2012 and after every test I delete the database and start over.

In my case I would say that the integration tests are much more important then the unit tests. This is because my application does not have so much logic, instead it is displaying data from the database in different ways.

Upvotes: 1

Martin
Martin

Reputation: 930

You would most certainly mock out the dependency to the database, and then assert on your service calling the expected method on your mock. I commend you for trying to follow best practices, and encourage you to stay on this path. As you have now realized, as you go along you will start adding new dependencies to the classes you write. I would strongly advise you to satisfy these dependencies externally, as in create an interface IUserRepository, so you can mock it out, and pass an IUserRepository into the constructor of your service. You would then store this in an instance variable and call the methods (i.e. _userRepository.StoreUser(user)) you need on it. The advantage of that is, that it is very easy to satisfy these dependencies from your test classes, and that you can worry about instantiating of your objects, and your lifecycle management as a separate concern.

tl;dr: create a mock!

Upvotes: 1

Related Questions