Kingamere
Kingamere

Reputation: 10146

How is unit testing testing anything?

I don't understand how I'm testing anything with unit testing.

Suppose I am testing that my repository class can retrieve values from the database correctly. The proper way to do this would be to actually call the real database and retrieve and check those values.

But the idea behind unit testing is that it should be done in isolation, and connecting to a running database is not isolation. So what is usually done is to mock or stub the database.

But why would testing on a fake database with hardcoded data and hardcoded return values even test anything? It seems tautological and a waste of time.

Or am I not understanding how to unit test properly?

Does one even unit test database calls?

Upvotes: 1

Views: 143

Answers (3)

Fabio
Fabio

Reputation: 32445

You can test your code against actual database in isolation. Just create new database instance for every test, or execute tests synchronously one after another and clean database before next test.

But using actual database will make your tests slow, which will slow down your work, because you want quick feedback on what you are doing.

Do not test every class - test main feature logic, which can use many different classes and mock/stub only dependencies which makes tests slow.

Find your application boundaries and tests logic between them without mocking.
For example in trivial web api application boundaries can be:
- controller action -> request(input)
- controller action -> response(output)
- database -> side effect of received request.

Assume we live in perfect world where new database and web server setup will takes milliseconds. Then you will tests whole pipeline of your application:
1. Configure database for test
2. Send request to the web api server
3. Assert that response contains expected data
4. Assert that database state changed as expected

But in now days world your boundaries will be controller action and abstracted database access point. Which makes your test look like below:
1. Configure mocked database access point(repository)
2. Call controller action with given parameters
3. Assert that action returns expected result
4. Possibly assert that mocked repository received expected update arguments.

If your application have no logic, just read/update data from database - test with actual database or, if your database framework allows it, use database in-memory.

Upvotes: 0

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57367

I don't understand how I'm testing anything with unit testing.

Short answer: you are testing the logic, and leaving out the side effects.

You aren't testing everything; but you are testing something.

Furthermore, if you keep in mind that you aren't really testing the code with side effects, then you are motivated to arrange your code so that the pieces that actually depend on the side effect are small. The big pieces don't actually care where the data comes from, to those are easy to test.

So "something" can be "most things".

There is an impedance problem -- if your test doubles impersonate the production originals inadequately, then some of your test results will be inaccurate.

my philosophy is to test as little as possible to reach a given level of confidence

Kent Beck, 2008

One way of imagining "as little as possible" is to think in terms of cost -- we're aiming for a given confidence level, so we want to achieve as much of that confidence as we can using cheap unit tests, and then make up the difference with more expensive techniques.

Cory Benfield's talk Building Protocol Libraries the Right Way describes an example of the kind of separation we're talking about here. The logic of how to parse an HTTP message is separable from the problem of reading the bytes. If you make the complicated part easy to test, and the hard to test part too simple to fail, your chances of succeeding are quite good.

Upvotes: 2

bcarlso
bcarlso

Reputation: 2345

I think your concern is valid. For me, TDD is more of an evolutionary design practice than unit testing practice, but I'll save that for another discussion.

In your example, what we are really testing is that the logic contained within your individual classes is sound. By stubbing the data coming from the database you have a controlled scenario that you can ensure your code works for that particular scenario. This makes it much easier to ensure full test coverage for all data scenarios. You're correct that this really doesn't test the whole system end to end, but the point is to reduce the overall test maintenance costs and enable faster feedback.

My approach is to mock most collaborators at the unit test level, then write acceptance tests at the integration test level, which validates your system using real data. Because the unit tests with their mocked data allows you to test various data scenarios out, you only need to test a few of those scenarios using integration tests to feel confident that your code will perform as you expect.

Upvotes: 1

Related Questions