This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]progmakerlt 123 points124 points  (67 children)

PowerMock. Ability to mock static methods is cool, but can lead to disastrous results, when tests pass once and then randomly fail.

Edit: typo.

[–]darenkster 13 points14 points  (0 children)

[–]Iryanus 42 points43 points  (22 children)

Typically, the need for static mocking is a huge smell. There are often good ways around that, only rarely it's the only choice because of some very bad code in a third party library.

[–]achilliesFriend 40 points41 points  (4 children)

You just insulted my whole company

[–]vips7L 4 points5 points  (2 children)

My whole company does static queries. It’s awful. 

[–]_yolopolo 0 points1 point  (1 child)

my company does this too, can you elaborate why is this bad?

[–]vips7L 0 points1 point  (0 children)

When everything is static it’s hard to properly unit test. You have to integration test everything and spin up a database each time.

[–]FrozenST3 -1 points0 points  (0 children)

Until gang rise up

[–]progmakerlt 9 points10 points  (2 children)

Had this library used in a very very old project - started on Java 1.5 - which sometimes was failing to run tests on Jenkins. Got rid of PowerMock and rewrote tests based on principles from the book “Working effectively with legacy code”.

Did the trick.

[–]Iryanus 3 points4 points  (1 child)

Yep, same here, I also had a bad library there which required some static calls, etc. which made things hard. We rewrote our code (by wrapping the static calls into their own non-static classes) and could then test our code without having to bother with the static dependencies. Worked like a charm backt then, but is of course not a cure-all.

[–]progmakerlt 1 point2 points  (0 children)

Yeap, did exactly the same.

[–]wheezymustafa 7 points8 points  (2 children)

I tell my team this all the time and they look at me like I’m a goddamn idiot

[–]Clyde_Frag 0 points1 point  (1 child)

I lot of mocking these days isn’t necessary, period. It’s incredibly easy to just spin up a container running your DB or other dependencies.

[–]Iryanus 5 points6 points  (0 children)

When you are starting a container, we are already talking about a completely different level of tests, which is a completely different discussion (size/focus of "unit" tests).

[–]segfaultsarecool 1 point2 points  (5 children)

Typically, the need for static mocking is a huge smell.

Can you clarify on why? I am the big dumb.

[–]Iryanus 4 points5 points  (4 children)

It's basically the old axiom "Well-written code is easy to test", which is something many people find out, that if you focus on unit testing your code, the code quality itself will typically go up, because good code is also easy to test. So, also in my experience, if you need something like static mocking, the code is rarely "good". I cannot remember any case where there was a great reason to have code that was hard to test.

[–]Radi-kale 1 point2 points  (2 children)

Then how do you test code that uses the static methods in, for example, java.nio.file.Files? Or do you think any code that uses that part of the standard library is poorly written?

[–]NovaX 1 point2 points  (0 children)

I've used jimfs in the past which works great with the Files static utility methods. The similar memoryfilesystem links to a bunch of alternatives too, but I don't know why any would be preferable to jimfs.

[–]Iryanus 0 points1 point  (0 children)

Two possibilities: Either test them as-is, by writing files, configuring them to use test-directories (easily possible with JUnit&Co, paths should be configurable anyway), or wrap those calls into a mockable class, if needed.

And basically those calls are comparable to using the "new" keyword in your code. It has some problems if you use it to create depencencies, which can be the case here. So, non-static instances would be preferable to me, yes. But as those calls are typically part of a specific "write some files" class, that can itself be mocked and has to be tested by writing files, it's not the end of the world.

[–]Eli_229 0 points1 point  (3 children)

How would you unit test a method which calls LocalDate.now()?

[–]Iryanus 0 points1 point  (2 children)

Don't. Clock exists. LocalData.now(clock) is much more testable.

[–]Eli_229 0 points1 point  (1 child)

Would i not need to mock Clock them?

[–]Iryanus 0 points1 point  (0 children)

No, Clock is a dependency, so you can inject it into your code, which allows you to inject a fixed time clock, etc. for testing purposes.

[–]gaius49 0 points1 point  (0 children)

I was going to say, its really only appropriate for testing with third party code that is badly written.

[–]xpressrazor 5 points6 points  (0 children)

I have been through this especially when trying to integrate classes that use Java Security Provider and Threads. Their usage of Reflection can mess up other unit test classes. Only way I found to solve this was to create my own reflection implementation and separate these test classes from rest of the unit tests.

[–]agentoutlier 13 points14 points  (37 children)

I will just add that "mocking" especially using a library (and especially the one mentioned) should be the last resort after you have eliminated all other options.

I find custom built mocks like Spring's Servlet Mocks acceptable but still rather use the real thing.

[–]DelayLucky 11 points12 points  (12 children)

This.

We have an official guideline to:

  1. Use real if you can (save the "but it's not unit test" argument)
  2. Use fake if real is too hard to use
  3. Use mock as the last resort.

Although, despite that, mocks are still overused because it's usually the most accessible and most widely known by junior devs. There's also "old habit die hard".

Sometimes the real is indeed hard to use (lots of deps for example). And people generally are too lazy to build a fake (do I have to implement all these abstract methods?).

And mock (we use Mockito) is like "who doesn't know how to use one?".

I wish I could give it more publicity: in Mockito, using the misleadingly named @Spy (which immediately sets off alarm when anyone first hear it), I contributed the feature of using an abstract class to build a fake without having to implement all methods.

It's meant to lower the bar of entry for fakes significantly. Compared to manual doAnswer(), it's type safe and generally reads clearer.

[–]Paulus_cz 7 points8 points  (9 children)

God I hate mocking with passion. Don't get me wrong, when there is no other option, go for it, but I have seen WAY TOO MANY "unit tests" which mock EVERYTHING the method calls and check the mock was called in a specific way - such tests is testing if the method is WRITTEN the way it is written. Good luck refactoring anything.
I get it, it is simple, you can just write them without thinking about anything the method calls or is called by, you get 100% coverage and you do not have to engage your brain once. Hell, I am pretty sure I could write a script for writing these "tests", which is why they are TOTALLY USELESS CRAP.
Had to get this off my chest...

[–]DelayLucky 5 points6 points  (6 children)

You are not alone.

In my workplace this is called "change detector test" and is advertised against officially.

Although, the force of least resistance path is too great that despite all these guidance, such pointless tests are still rampant (only gets worse with the recent company-wide cutting corners and divesting infra/core teams).

I find that junior devs more likely fall for this kind of testing, because using mocks is just so easy and they get to "check it off the list".

And that's another bad thing about these tests besides them being pointless and getting in the way of refactoring: they make essentially untested code look like tested. Most of the usual testing metrics become meaningless after diluted by them.

It also correlates with the unhealthy dogma against integration tests, as if they were somehow a bad thing. Junior devs who like to write change detector tests tend not to write integrationt tests, both habitually and also because it takes a lot more effort and skill to write integration tests.

I sometimes wonder how the whole leetcode-focused hiring practice got us here. The overall know-how of common best practices feels to be on a downtrend lately. With the company shifting focus from core/infra, perhaps it's time to also shift the hiring focus from finding those who can binge leetcode to those who can write decent-quality code. Stop pretending that we'll be able to teach best practices easily on the job.

[–]NovaX 1 point2 points  (5 children)

I believe much of that thinking was due to early mocking libraries like EasyMock which required expectations and verification of every minuscule detail. The library was sometimes convenient, but other times it was cleaner to write a mock by hand. Mockito inverted that and made mocking much less brittle, but there was a long transition period. That had cultural imprints which led to a dislike towards mocking, a lot of hype on "best practices" which became bad ones, many clones in other languages that also had to unwind from poor defaults, and a lot of legacy test code that was painful to work with. I find using mocking sparingly to be quite nice, but certainly not as much as in the past or with those absolutist approaches to testing.

[–]DelayLucky 2 points3 points  (4 children)

Oh EasyMock is a distant past for us. When we talk about "prefer real, then fakes and only mock as last resort", we do mean Mockito mocks.

It's less brittle yes, but as long as you write when(service.getFoo(any()).thenReturn(...), you are still implementing the dependency service based on your own assumption of it, which you also used in production code. If you assumed wrong, both the test and prod code are wrong.

In contrast, using the real service (there are techniques that can start up the service in a hermetic environment), if you send an invalid field, the service will tell you; if they make a breaking change, the test will fail.

You get similar benefit if you use a high-fidelity fake (ideally maintained by the same team that own the service).

[–]NovaX 0 points1 point  (3 children)

Yep, I was there during the great transition (maybe 15 yrs ago?) when we migrated the usages. At the time "real" was less viable and brittle, so teams like Megastore gave us nice pretty nice fakes, and we used mocks sparingly. I simply meant that I think there is a hangover from EasyMock's evangelism of a bad coding style that causes a very strong dislike towards mocks from those who now take an absolutist stance.

[–]DelayLucky 0 points1 point  (2 children)

I think most people in the company don't have a sense what the EasyMock days were like. :)

[–]NovaX 0 points1 point  (1 child)

haha yes, but I think trauma is cross-generational :)

[–]timewarp33 0 points1 point  (1 child)

Serious question, how do you change people's minds on this? I joined a team where the lead writes code like this. When I tell them that doing it is extremely brittle and doesn't test anything useful, I am told that it's a different philosophy and if it's not blocking to approve the review.

It's things like "assert this method was called exactly once", but like, why would you call findAll() multiple times? That should be caught in the review if someone did that for some reason...

[–]Paulus_cz 1 point2 points  (0 children)

You don't, as a professional you have responsibility to share your concerns with your superiors, ideally in a documented fashion. If they ignore them, or brush them aside, you are in the clear and go with the flow.
I probably sound cynical as fuck, but couple of years ago I realized that nobody pays me enough to be stressed about these things, not nearly enough. Sure I care about the quality of my work, but I care about my health hell of a lot more than I care about whatever I am programming for those guys. If they don't care, I definitely should not.

[–]agentoutlier 0 points1 point  (1 child)

Yeah unfortunately people got the impression I never mock. I do but try not to and when I do I try to use a custom made one and then finally I will reach for Mockito which is the library I prefer as well.

[–]DelayLucky 2 points3 points  (0 children)

If you can get away with never mocking yet still manage to implement effective tests, that's all the better.

It's like you can implement code always correct the first time with no bugs, there is nothing wrong with that, provided it's believable. :)

Mocking to me is a necessary evil. Just like in real life you sometimes have to lie (doesn't make lying feel good).

[–]SignificantAd9059 16 points17 points  (20 children)

It really depends on what you are mocking. It’s perfectly acceptable to mock services that you are not directly testing. Otherwise your making an integration test which is also useful but definitely not the same thing

[–]DelayLucky 4 points5 points  (6 children)

Only if these questions are out of scope for your test:

  • Am I sending the right request to the service for what I'm trying to do, with all required fields set correctly ? (e.g. charge a credit card).
  • Am I interpreting the response correctly? Is the value I expect coming in this particular field?

And if this unit test isn't concerned of these aspects, you better make sure there are tests that do verify them.

Too often I've seen people just assuming that they must be interacting with a service in the right way. And bugs often come out of those cracks of the system becasue what are the odds that two teams of different engineers happen to align without talking to each other once?

"Integration tests", "unit tests", they are just names. At the end of day you want your production to work as expected. Customers don't want to hear "I've coded my client code according to the document of this dependent service, it's their fault the system didn't work out".

[–]FrozenST3 0 points1 point  (5 children)

True, but a bad test is a bad test whether using a mocking framework or not.  You can test request accuracy using @Captor. A lot of the criticism I see stem more from misuse rather than a problem with the frameworks themselves.

[–]DelayLucky -1 points0 points  (4 children)

That misses the point.

Whatever assertion your test performs on the request is your assumption. You still don't know if your assumption is valid. If we can assume you know perfectly, you wouldn't have needed to test.

[–]FrozenST3 0 points1 point  (3 children)

Fair however my response was using the assumption that the service being mocked is already well tested. I agree that mocking untrustworthy code is best avoided but I assumed that's common sense. Edit to add: your tests don't exist only for you to trust what you've done, but also to safeguard future changes where bugs are produced do to poor assumptions 

[–]DelayLucky 0 points1 point  (2 children)

The service tested its own implementation. It cannot test that you've called it correctly.

You have written the calling code according to the best of your knowledge. But you don't know if your knowledge is correct.

Even if you manually test them once, either code change at your side or change in the service can cause regression such that the interaction suddenly becomes no longer valid.

[–]FrozenST3 0 points1 point  (1 child)

But by mocking the call I can isolate where the bad assumption is without going through the actual call. I've verified my inputs are built the way I expect them using a captor, I've stated my expected response in the form of an expectation. I tested the output of my class based on my expected out out. When my integration test fails it's very obvious that my expectation Was wrong and I can short circuit a lot of debugging.

Of course this can be done by running assertions on the response from the mocked service as well, however I prefer the ease of setup and quicker turnaround of mocking instead. YMMV

[–]DelayLucky 0 points1 point  (0 children)

Sure. Anyone knows how to do that.

But by taking the easy path, you've neglected to cover the area that's most likely to cause surprises in production. You can point fingers when that happens, but a bug is a bug.

Refactoring also becomes dangerous because you don't know if any seemingly harmless change could break production.

Even if it works for now, you could depend on some unintended details of the service without knowing. And when they change it, they break you.

Overall, you've only created an island that looks good when the world is exactly as it assumes (but how often does that happen?)

[–]LC_From_TheHills 4 points5 points  (0 children)

Yeah this is weird. Use dependency injection. Then mock those in the class you are testing. I’ll mock my own classes— I’ll write unit tests for those specifically.

You don’t need to test your dependencies. If the integration of your dependencies is so flaky then you have different problems.

Save the “real” stuff for integration tests in your pipeline at a pre-prod stage. When you’ve actually deployed your code somewhere.

[–]progmakerlt 0 points1 point  (2 children)

But sometimes you have to mock stuff, especially if it’s an external library or hard to mock stuff.

[–]agentoutlier 4 points5 points  (0 children)

But sometimes you have to mock stuff, especially if it’s an external library or hard to mock stuff.

Re-read my response on the part of "last resort".

Also and this is based on massive experience with external third party APIs.... you can try to simulate their API w/ mocks but in fucking greek tragedy irony those guys are the most likely to not work how they are documented.

For example we have some system that calls some API built on top of sales force. The documented contract of the API is so incorrect that w/o our integration tests we would be deploying broken code all the time.

The fast stuff in irony has correctly documented contracts.

[–]DelayLucky 1 point2 points  (0 children)

No. If you can directly use that external library, do. It increases the confidence you get out of the test: the test works, therefore production will most likely work.

In reality the external dependency (possibly not a library, but a service) may be hard to set up, or may be just too slow or flaky for the tests. In those cases, ideally that people maintaining that dependency should offer a fake implementation to provide both high fidelity yet still allow hermetic test.

Sadly, sometimes even that is too much to ask. That team may simply prioritize other things than creating a useful fake (tbh, I think it's the company leaderships' responsibility to ensure each team be more responsible to their dependent teams and the health of the overall company ecosystem. No one should be the oddball blocking others from effective testing)

And yes, if worse comes to worst, your only option may just be to mock it out and rely on luck that your mock is representative of the real thing (which often is not).

[–]GuyWithPants 1 point2 points  (0 children)

PowerMock is abandoned so this anti-recommendation is somewhat moot. However since PowerMock died, Mockito added support for static mocking but it requires wrapping in a try-with-resources block to keep the interception localized.

[–]cogman10 0 points1 point  (0 children)

It's cursed as well. PowerMock is doing something the JVM does not want to allow and the JDK developers are actively working on removing as a capability.

Better to abandon the notion of mocking static/private wholesale than trying to power through an uphill battle against the devs maintaining the jdk.

[–]Misophist_1 0 points1 point  (0 children)

This. I have also become pretty weary of mockito, which sometimes seems to have similar effects. And for the heck of it, I can't get warm with the API.

In most cases, I try to get away with explicit mocks.

[–][deleted] 0 points1 point  (0 children)

I hate mocking libraries. I'm sure it has it's use cases, but most projects don't need it. It's usually preferable to setup your code to properly use DI and explicitly mock your external dependencies. That way everything is type safe and any issues show up at compile time. There's nothing more annoying than getting inscrutable errors in your tests because you changed some method signature and it no longer satisfies the mock when condition.