16

I have a set of JUnit tests which call the main method on a Java program, passes in args and checks the output. That's fine.

However, if the program I am testing has static values which are altered, they will remain the same between tests. This causes a problem. I have no control over exactly what the program is that is being tested, or the names used for static fields.

How can I ensure that my Unit tests run cleanly, as if it is starting the program from scratch, without keeping those static fields. Is there a way to reset them in some way?

If not, I will have to fire up a new process which runs the program, then check the output etc, but this seems a little overkill.

Edit - Please note I have no control over the code which the unit tests are testing - I can't change their field names, and unfortunately, I also won't know their field names. I am thinking that makes this not possible, without starting a new process?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
ThePerson
  • 3,048
  • 8
  • 43
  • 69
  • Per your edit. You are correct. Seems like these are not "unit" test but tests of a "library" (given that you have no control of the code nor will the tests be updated if the code is changed). In testing a library you should only test the contract. The fact that this "library" maintains state in a static field suggests this is not a well designed "library". – John B Oct 07 '14 at 13:43

4 Answers4

10

You should explicitly initialize any static state within your test classes, usually this is done in methods annotated @Before or @BeforeClass

This is a reason, among others, why having a lot of static dependencies in an application is a bad idea for testing. That's why many people encourage stateless programming.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
Joel
  • 2,374
  • 17
  • 26
  • OP has not control over the program he is starting where the static field are located. `@Before` `@BeforeClass` are related to the test class itself. – A4L Oct 07 '14 at 11:25
  • Clearing the variables in a method annotated with `@Before` in the test class will work. Cleaning the code under test to get rid of those static variables would be better. – Aaron Digulla Oct 07 '14 at 11:51
  • I have no control over that unfortunately (it's submitted by students, the Unit tests are part of the marking process). I'm not sure I understand what you mean by "cleaning the variables". – ThePerson Oct 07 '14 at 13:13
8

In general if you find your code to be untestable, like is the question here, it's a sign of a code smell, and you should seriously consider refactoring your code to not to use those static fields.

Having said that, you might find the BeanInject library helpful. You could put an @After annotated method into your test class and have it reset the static fields using the injection:

Inject.field("thatStaticField").of(thatObjectWithStaticFields).with("default value");

That way you only need to know the field names but you don't have to be able to actually modify the class with the fields. The library does that using reflection.

Additionally, it came to my mind that if you are testing something that contains parts that you cannot control, why don't you try to mock those parts with, say, Mockito?

EDIT/ADD: OK, so your issue is that you don't even know the initial values of the possible static variables the classes may or may not have. I see two possible approaches: 1) You'd have to either save their values when the class is loaded for the first time and reset the values between each test, or 2) you have to get an entirely new instance of the class from the class loader.

On point 1), you'd need to use reflection to loop through all the fields in your @BeforeClass method, save their initial values into some Map<String,Object> structure, and then reset the values in your @Before or @After method. Here's some topic on looping through the fields of a class using reflection: Loop over all fields in a Java class

Regarding point 2), you have the instructions for that (involving class loaders) here: Java: how to "restart" a static class?

It's pretty cool what you can do with reflection and that stuff. :)

Community
  • 1
  • 1
ZeroOne
  • 3,041
  • 3
  • 31
  • 52
  • Thanks - This is good, but the issue here is that I have no knowledge of the code. The code is actually submitted by a student, so exactly what they call their objects is out of my control. I think this is almost what I want, but I would need to do it for all fields. My alternative seems to be to run a new process for each test, but as mentioned in the question - it seems a little over the top and unnecessary. – ThePerson Oct 07 '14 at 13:12
  • Alright, I can see the problem more clearly now. It is indeed a tricky situation. Please see my revised answer. – ZeroOne Oct 07 '14 at 13:41
  • I suppose i'm fighting an battle which can't be won, as actually I also only know the name of their class which contains the main and not any other classes. I found reflection to be pretty cool too, but I think with this one i'm going to be better off starting a process for each unit test - that way they can't (easily) interfere with each other. Efficiency is important, but it's not a show stopper at the moment for my project. – ThePerson Oct 08 '14 at 14:13
5

Take a look at this post: Set Private Static Field. Unlike BeanInject or ReflectionTestUtils (which I use a lot), this mechanism does not require a instance of the class. Since this is a static field I wasn't sure if you had an instance. If you do, use one of the two above.

Copied from post:

 public static void main(String[] args) throws Exception
{
    Field field = MyClass.class.getDeclaredField("woot");
    field.setAccessible(true);
    field.set(null, "New value");
}

I was surprised to see that ReflectionTestUtils required an instance. Seems like it should be able to handle this case. Too bad.

As others have stated, do this in the @Before method to ensure state BEFORE your test begins. Doing so in the @After is error prone as it assumes that you other test might affect the state of the static field.

Community
  • 1
  • 1
John B
  • 32,493
  • 6
  • 77
  • 98
  • My main issue here is that "woot" could be "foo" or "bar", as unfortunately I have no control. This is for unit tests that are to be ran automatically, so the idea would be without having to go through and find all their static object names. For the vast majority, it's fine... but for some that keep static references (E.g, to System.in), this causes a big problem. – ThePerson Oct 07 '14 at 13:15
  • 1
    If you don't have enough knowledge or control of the code to know what field you should be resetting and therefore what should be the appropriate value to reset it, you shouldn't be changing it in unit tests. Unit tests are written alongside and with an understanding of the code under test. It should be the responsibility of the developer to update the unit tests if the code is being updated. – John B Oct 07 '14 at 13:40
0

I know this is an old question, but i had a similar problem that i solved using "org.mockito.Mockito.clearAllCaches" in method annoted with @AfterAll

note: I am using junit 5.

Example:

// here the static field are beeing mocked
@BeforeAll
static void setUp() {
    mockStatic(Static variable);
}

// here all mocks are beeing excluded, including the static one
@AfterAll
static void reset() {
    clearAllCaches();
}
Zidan
  • 1