DEV Community

Discussion on: Unit testing: best practices

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

I disagree with the need for mocking and I think people spend way too much time setting up mocks. The rest of your modules should alos work, since you've tested them as well, thus it's totally fine to rely on them working to write new tests.

The goal is, as you said, to test business needs. There isn't a strong objective to have modules tested independently.

This isn't to say that mocks aren't useful tools, just that they are being too widely used in situations when they just aren't necessary.

Collapse
 
ice_lenor profile image
Elena

I think I understand your point of view - you are talking about integration testing.

But I have an objection. When your code is complex enough and has several nested dependencies, it becomes very hard to test all combinations of all code paths with integration testing.
Imagine this:
{code}
class A {
void methodA(B b) {
if(condition) {
b.methodB1();
} else {
b.methodB2();
}
}

class B {
void methodB1() {
if(condition) {
doSomething();
} else {
doSomethingElse();
}
}
void methodB2() {
if(condition) {
doSomething2();
} else {
doSomething3();
}
}
{code}
If we don't mock while testing class A, we end up with a dependency on a particular implementation of B. How many tests for A we need to write now?

If in the code we use different B's, say, different subclasses, do we need to test these combinations as well? This is a lot of tests for A.

Now, if there is a problem in B's implementation, all tests for B and A will break, and it will be harder to understand where's the problem.

I think there should be a healthy combination of pure unit-tests and integration tests. In case of doubt, and especially if the logic is complex, I would prefer to have unit-tests as well.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

If you're testing A you still limit your testing to A, on the assumption that B is working. That is, just becasue B branches on a condition doesn't mean you test it in A's tests.

What you're essentially doing is just using a current implementation of B as the mock object. You know how it works and rely on that for the testing of A. This is sufficient to demonstrate that A is working correctly.

If B breaks, then yes, tests in A will also break, but so will tests in B. Unless you have circular dependencies you still have a tree of changes to work through in order.

Keep in mind the primary value of unit tests is to ensure things are working. Debugging is a secondary value. Getting more coverage by reusing code is of more immediate value than getting clean mocks. I also start with the breadth of coverage, and if, and only if, debugging is proving problematic, do I start writing focused mock objects.

Thread Thread
 
nestedsoftware profile image
Nested Software • Edited

I agree with edA-qa on this point. Creating multitudes of mocks just to keep unit tests 'pure' seems like a great deal of effort to create and maintain additional code, and the benefit of doing so seems unclear to me. As edA-qa said, the real code can usually do double-duty as a test object.

There are cases for using mocks, but my opinion is that they should be introduced when depending on the real application logic in a test causes a problem. That can be for performance reasons; possibly to reduce the amount of setup/configuration needed just to run unit tests; perhaps because the real code may not be available when tests run; often mocks are used because time affects the results of the real code - in fact I imagine that things like the current system time and calls to random are some of the most often mocked pieces of logic.

Note: For this comment, by 'mock' I mean any kind of stand-in used for a test that replaces the actual application code. That includes 'mocks', 'stubs', 'fakes'...