I know how to access private variables, but I have the following class that I'm trying to test:
ProcessStatusResult:
@Getter
class ProcessStatusBody {
public ProcessStatusBody(ProcessStatus status) {
this.status = status;
}
ProcessStatus status;
}
@Getter
public class ProcessStatusResult {
ProcessStatusBody body;
...
public ProcessStatusResult(ProcessStatus status) {
body = new ProcessStatusBody(status);
...
}
}
In my test, I need to get the ProcessStatus inside ProcessStatusBody to validate it, but I have no idea how to do that.
Is there a way to use reflection (or some other method) to access this without having to add a getter in ProcessStatusResult for the sake of the test alone?
CodePudding user response:
There are broadly speaking 3 different ways to tackle this problem.
The common way - (ab) use package private.
It's common to stick (unit1) tests in the same package as the code it tests. Different 'source root', same package. You'd have:
src/main/java/com/foo/Whatever.java
src/test/java/com/foo/TestWhatever.java
When you do this, your test code can just 'see' all package private stuff (because, same package).
Some projects would like to highlight this a bit and have an annotation that indicates 'this item is package private for the sake of test code; we would have made it private otherwise', and if you really want to go all-out, you add linter tools that detect any attempt to interact with entities so marked from other source files in the same package (not the test code) and flag it as an error.
These tools are, in my experience, unfortunately not commonly used and I'm not readily aware of linter tools that do this. A somewhat common annotation for this is guava's @VisibleForTesting
. It doesn't do anything except serve as documentation (for now - one could write a linter tool that actually checks for style violations, of course).
The uncommon way, reflection and mocking
Test frameworks can do this but it severely hampers productivity. If you want to access your status
variable, all you need to do is type body.
and hit CTRL SPACE or equivalent in your IDE and it'll be shown right there, you can just select it, you can CMD click once you type or auto-complete it to be taken straight to its definition, and if you typo the name, your IDE will red-wavy-underline it.
Replace body.status
with something like reflect.getField(body, "status", ProcessStatus.class);
and it's a heck of a mess. It's long, ugly, mismatches generics to types (what if status
is of type List<String>
? List<String>.class
isn't valid java. You could use reflection hackery to allow you to assign reflect.getField
to anything you please, but now you have no type safety. If you mess up the type, you won't know until you run it. If you mess up the generics, you may not ever know, ouch.
Hence, this isn't great either, and is consequently not used often.
The nuanced way: That's not how it works.
private
perhaps just means private. It's not meant for consumption by anything 'external to the source' file and that includes the unit tests. No source file consists solely of 100% private
everything - there is some API you're supposed to plug into from 'outside', and test code can get at everything except private
(so, package private, protected
, and public
is all accessible by test code). Test the public API parts, don't test the private stuff - after all, the normal idea behind private
is that you're free to mess with these as much as you please, change what they do, change their signatures, all you need to check is what impact that has based solely on what else is in this source file you are looking at.
There is benefit in not having to extend that to: "... oh and you also need to revisit all the unit tests that interact directly with this".
Make an actual public-ish API (package private if you wish, but you write it with the actual intent that code from outside this source file will want to use this feature) and test that. Or, if truly this field is not a detail that even other source files in the same package are meant to know about then do not test it - it's too fine grained a test.
There are upsides and downsides to testing at such incredibly fine grained levels. Generally, that level of fine-grainedness makes tests simpler and more 'localized' (any error the tests catch will be pointing right at the actual problem), but going that deeply means you need a heck of a lot of tests, most tests turn into the trivial, and it creates significant resistance to refactoring - after all, even a simple refactor requires modifying a lot of tests as well. You're also risking "Copycat syndrome", where the same programmer writes both the unit test and the code that is being tested, and any mental error is therefore likely to exist in both, thus making the test completely useless.
A solution to copycat syndrome is to disentangle the writing of the test with the writing of the code. This is obviously easy to do when one person writes the test and another writes the implementation, but you can do it on your own - just make sure there's time and a 'mental break' between writing tests and writing the implementation, enough to prevent your brain from replaying the exact same reasoning when writing either (which is the likely path to making the same error twice in a row without realizing).
However, when you disentangle like this, it becomes really weird and unwieldy to test with the granularity of 'testing the private stuff' - testing like this tends to follow the notion of 'there is some API spec of sorts and that is what we test', and private stuff by definition isn't part of such a spec.
Whether you like to write the tests first, or the tests last - the same principle applies. It's bizarre to write tests and, during the writing of them, already start cooking up the private methods that you envision will be required to implement it: That's not your 'job' as test writer.
It's similarly strange (slightly less so, perhaps) to write tests that test implementation choices.
SO isn't intended for opinion so I won't spend more than two sentences on it, but my strong suggestion is that third option. private
means: Not to be tested directly.
1) Really, any test where the scope of what it tests is clearly restricted to some 'unit' of code that in your project's code style rules requires being in a single package. The two concepts ('what do I stuff in packages' and 'what does this test' need to at least line up, or 'what do I test' needs to be more finegrained than that). In other words, integration tests that test the entire project don't get to use this strategy at all, but then if those are testing private variables, I'm pretty sure you're doing something wrong.
CodePudding user response:
Well, here you have some working code using reflection:
// Assuming you have the objects
ProcessStatusResult processStatusResult = new ProcessStatusResult(new ProcessStatus());
ProcessStatusBody processStatusBody = processStatusResult.getBody();
try {
// The status field, you know the name, right?
Field privateField = ProcessStatusBody.class.getDeclaredField("status");
// Set the accessibility to true since it is not visible
privateField.setAccessible(true);
// Here we go
ProcessStatus status = (ProcessStatus) privateField.get(processStatusBody);
System.out.println(status);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
// do something
}
Sorry, I didn't realize you said: "Is there a way to use reflection..."