2

I have a method that calls a Util class' static method.
Is there a way to mock the return value of this static method so that it always returns a static value?

I want to do this because this static method returns me some configurations that could change, but I don't want this to affect my test. If I could mock this Util class and return a static results for the method all the time, it would give me a stable test.

public class SomeService{
  public void doSth(){
    SomeConfig config = UtilClass.getConfigForId(“id”);
    ...
  }
}

I tried the mockStatic method mentioned here but it didn't work:

// get SomeService instance from Guice container
Injector injector = Guice.createInjector(new BasicModule());
SomeService someService = injector.getInstance(SomeService.class);

try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
  
  dummy.when(() -> UtilClass.getConfigForId(any()))
        .thenReturn(someConfig);
  
  // Didn't give me someConfig above, but the actual value
  someService.doSth();
}

UPDATE:

Apologies for all the typos made. I've now corrected them so that the code is syntax-error free.
I think I found what might have broke the MockedStatic: that UtilClass.getConfigForId is called in another thread. Here's the overall structure:

public class SomeService{
  public void doSth(List<Msg> messages){
    List<Callable<Void>> tasks = new ArrayList<>();
    
    for (Msg msg : messages){
      tasks.add(()->{
            String id = msg.getId();
            SomeConfig config = UtilClass.getConfigForId(id);
            ...
      })
    }
    

    // use Executors.newWorkStealingPool() to execute the tasks
    ...
  }
}

As long as UtilClass.getConfigForId(id) is not executed in the same thread as the try with resource code, the magic doesn't work. I've tried another function that does the same but in one thread and MockedStatic works there fine.
So the question becomes: how to make MockedStatic work in a different thread?
Another UPDATE:
I think from the javaDoc it's not possbile. So I'll have to come up with other ways, e.g. refactoring

waynewingorc
  • 169
  • 9
  • 2
    In the code you posted, you mock the service class, not the util class `Mockito.mockStatic(SomeService.class)` – XtremeBaumer Jan 11 '23 at 11:05
  • @XtremeBaumer is right - the test code does not even compile (incompatible types). – Jonasz Jan 11 '23 at 18:06
  • Sorry about the typo @XtremeBaumer. I've corrected them. And I seem to have found out why `MockedStatic` doesn't work for me: that it's called by another thread. – waynewingorc Jan 11 '23 at 23:35

1 Answers1

1

Your tested code doesn't seem to match your test code. In SomeService#doSth you're calling UtilClass.getConfigForId(), yet in the test you're mocking a method with different signature: UtilClass.getConfigForId("id").

If you use a method with String parameter in your code, the stubbing has to match it, so if in the test you expect a value "id", it should be passed to the method in the tested code (SomeService) as well (values are compared using equals method in Mockito by default). Otherwise, you should use an ArgumentMatcher like any().

var id = "id";
var mockedValue = "test value";
var someConfig = new SomeConfig(mockedValue);
var someService = new SomeService();
try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
    dummy.when(() -> UtilClass.getConfigForId(id))
         .thenReturn(someConfig);

    var result = someService.doSth(id);

    assertEquals(mockedValue, result);
}
var mockedValue = "test value 2";
var someConfig = new SomeConfig(mockedValue);
var someService = new SomeService();
try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
    dummy.when(() -> UtilClass.getConfigForId(any()))
         .thenReturn(someConfig);

    var result = someService.doSth("some id");

    assertEquals(mockedValue, result);
}

If the signature of the method you're using in the tested code does not include a parameter, it should be mocked without a parameter as well (in case of for example an overloaded method).


I've tested the code above and all tests pass - you can verify it in a GitHub repository.

Jonasz
  • 1,617
  • 1
  • 13
  • 19
  • Thanks for the great answer and code @Jonasz. Apologies for the typo in my question - I double checked and can make sure that signatures and the param values match in my code. So that still doesn't work for me. Would it have anything to do with that my `someService` in the test is managed by Guice (something like Spring)? – waynewingorc Jan 11 '23 at 05:32
  • 1
    Could you please make the code in your question reflect your actual code as much as possible (including the whole test method)? It's hard to guess, unfortunately, but I don't think dependency injection tool like Guice would cause problems with `mockStatic`, so I suppose it could be something different. – Jonasz Jan 11 '23 at 06:31
  • Jonasz I've updated the question. You're right the injection tool has nothing to do it but multithreading does. Do you mind checking my update? – waynewingorc Jan 11 '23 at 23:31
  • 1
    You're right in your last update though - mocking static methods should be used only when you have no control over the tested code, otherwise it's better to avoid such static methods as they make the code much harder to test. It would be better to use DI (since you're already using Guice) to inject the class creating/loading the config and replace it in the test with a mock you'd have full control over. The config provider class should have it's own tests as well. – Jonasz Jan 12 '23 at 05:51