Sweta Sharma
Sweta Sharma

Reputation: 2784

How to write a unit test for static variable?

I have a class that has a static variable and a function that makes use of that variable. I need to write a unit test for shouldCreateNewToken() method.

public class Auth {
    private static Instant tokenExpiration = Instant.now();

    public boolean shouldCreateNewToken() {
        Instant currentTimestamp = Instant.now(); 
        Duration differenceInTime = Duration.between(currentTimestamp, tokenExpiration);

        if (currentTimestamp.compareTo(tokenExpiration) > 0 || differenceInTime.getSeconds() < 120) {
            return true;
        }   
        return false;
    }
}

Upvotes: 1

Views: 7809

Answers (3)

tquadrat
tquadrat

Reputation: 4054

In the current form, you cannot test that code.

While you can still use Reflection to modify Auth.tokenExpiration for testing, you fail to set the local variable currentTimestamp.

Modify your code like this:

public class Auth 
{
  private static Clock clock = Clock.systemUTC(); // NOT final!
  private static Instant tokenExpiration = Instant.now( clock );  // NOT final!

  public boolean shouldCreateNewToken() 
  {
    Instant currentTimestamp = Instant.now( clock ); 
    Duration differenceInTime = Duration.between( currentTimestamp, tokenExpiration );

    if( currentTimestamp.isAfter( tokenExpiration ) || differenceInTime.getSeconds() < 120) 
    {
      return true;
    }   
    return false;
  }
}

This allows you to manipulate the time: via reflection you can set the Auth.tokenExpiration to an arbitrary instant, then you modify Auth.clock before each call to Auth.shouldCreateNewToken().

Have a look to the Javadoc for Clock for the details.

I assume you do not need help on how to use Reflection or on how to write the test itself?

Upvotes: 1

AlexMelgar
AlexMelgar

Reputation: 96

  • I may not have enough context on how you're planning to use the Auth class, however, base on what I can see, your shouldCreateNewToken method will ALWAYS return true.

    Static variables are created before the main is executed. When your program/service is initialized, you'll already have a value for tokenExpiration (let's say 10), and when you're method is executed, it will always pick a value greater than the tokenExpiration (let's say 11,12,...)

    Here I shared a quick guide to understand that behavior: https://www.baeldung.com/java-static-variables-initialization

  • About the mock question you have, there are 2 approaches which could be used:

    1. You could use mockStatic from Mockito, or even PowerMockito has a similar approach (depending what you use/prefer), and mock the Instant object

      Instant instant = Instant.now();
      
      try (MockedStatic<Instant> mockedInstant = Mockito.mockStatic(Instant.class)) {
          mockedInstant.when(now()).thenReturn(instant);
          ...
      }
      
    2. The second approach is to pass the tokenExpiration instant as parameter of the Auth class' constructor, this way you 1) avoid the mocking, allowing you to pass the instant you want, and 2) allows you to create different Auth configurations base on different needs.

      class Auth {
      
          private final Instant tokenExpiration;
      
          public Auth(Instant tokenExpiration) {
              this.tokenExpiration = tokenExpiration;
          }
      
          ...
      
      }
      

Upvotes: 1

QuentinC
QuentinC

Reputation: 14882

First of all, it's almost impossible to unit test something properly when static variables are involved. Changing the variable value or internal state have chances to leak in following tests, and it's much worse when several tests are ran in parallel (Surfire does run tests in parallel for example).

So the first thing to do is to refactor your code to don't use static variables. You should for example use dependency injection, if you are using a framework with that feature such as Spring.

Secondly, to test time sensitive stuff as it looks to be the case here, you can use clocks. As time is everywhere in applications, this isn't as easy to set up as it looks like, though.

Upvotes: 4

Related Questions