dredbound
dredbound

Reputation: 1669

Java Unittest Mocked Unittest Not working

I'm trying to create a unittest for the method below (myHostClient), but I'm having some problems with it:

MyClass.java

import com.abc.def.ServiceBuilder

public class MyClass {

    @Value("${materialLocation}")
    private String materialValue

    private static final SERVICEBUILDER = new ServiceBuilder()

    @Bean public MyHostServiceClient myHostClient(
        @Value(${qualifier_one}) final String qualiferOne,
        @Value(${use_service}) final boolean useService) {
        if(useService) {
            return SERVICEBUILDER
           .remote(MyHostServiceClient.class)
           .withConfig(qualifierOne)
           .withCall(new CallerAttach(Caller.retro(defaultStrategy())), // Error Line2 Here
              new SigningVisitor(new CredentialsProvider(materialValue))),
              call -> call.doSomeStuff(StuffObject.getStuffInstance()))
           .makeClient();
    }

    @Bean DefaultStrategy<Object> defaultStrategy() {
        final int valueA = 1;
        final int valueB = 2;
        return new DoSomeThingsBuilder()
            .retry(valueA)
            .doSomethingElse(valueB)
            .create();
    }
}

And here is my latest unsuccessful attempt at writing a unittest for it:

MyClassTest.java

import org.mockito.Mock
import static org.mockito.Mockito.times

public class MyClassTest {
    @Mock
    private SERVICEBUILDER serviceBuilder;
   
    private MyClass myClass;

    private String qualifierOne = "pass"

    @BeforeEach
    void setUp() {
        myClass = new MyClass();
    }
   
   @Test
   public void test_myHostClient() {
       boolean useService = true;
       
       final MyHostServiceClient result = myclass.myHostClient(qualifierOne, useService); // Error Line1 here
       verify(serviceBuilder, times(1));
   }
}

I have been trying to mock SERVICEBUILDER and verify that the mocked object is called one time but no luck so far. Right now I'm getting this error:

IllegalArgumentException: Material Name cannot be null

And it points to these lines in my code.

In the Test: final MyHostServiceClient result = myclass.myHostClient(qualifierOne, useService);

Which points to this line in the module: .withCall(new CallerAttach(Caller.retro(defaultStrategy())),

Anyone know how I can fix my unittest or write a working one from scratch?

Upvotes: 0

Views: 67

Answers (1)

Dmitry Khamitov
Dmitry Khamitov

Reputation: 3296

I would say the design of MyClass is quite wrong because it looks like a Spring configuration but apparently it's not. If it is really supposed to be a configuration then I wouldn't even test it like this because it would rather be an integration test. Of course, even in integration tests you can mock dependencies. But the test itself would run differently and you would have to spin up a suitable Spring context, etc.

So given the above, I would rather make MyClass some sort of MyHostServiceClientFactory with removing all of the Spring annotations and then fix the following problems in your code.

  1. SERVICEBUILDER is hardcoded.

SERVICEBUILDER is static final and its value is hardcoded into MyClass. You will not be able to reassign that field with the mocked version. It can still be final but not static then and it's better to use dependency injection here by passing the value through the MyClass constructor.

  1. SERVICEBUILDER will still be not mocked even if you fix the above.

To really mock SERVICEBUILDER by using the @Mock annotation in the test you should enable Mockito annotations.

If you are using JUnit5 then you should annotate your test class like this:

@ExtendWith(MockitoExtension.class)
public class MyClassTest {
    ...
}

If you are stuck with JUnit4 then you should use another combination:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
    ...
}

Once you've done that the SERVICEBUILDER will be mocked but now you will have to configure the behaviour of that mock, like what is going to be returned by the SERVICEBUILDER methods. I can see 4 methods in total, namely remote, withConfig, withCall, and makeClient. You will have to do Mockito's when/thenReturn configurations.

  1. MyClass.materialValue is null.

But even when your mock will be properly configured you will still encounter the original IllegalArgumentException: Material Name cannot be null. This is because MyClass.materialValue will still be null and looks like CredentialsProvider cannot accept that. As I can see, that field is supposed to be injected by Spring using the @Value annotation, but remember this class no longer contains anything from Spring. As in problem 1, you have to pass the value through the MyClass constructor.

Once all of these problems are solved you can introduce a thin Spring configuration like MyHostServiceClientConfiguration (or whatever name suits you) that would serve as a provider of necessary properties/dependencies for MyHostServiceClientFactory (existing MyClass) and then this factory can provide you with a MyHostServiceClient bean through a method like MyHostServiceClientConfiguration#myHostServiceClient annotated with @Bean.

Conceptually your MyHostServiceClientFactory will look like this:

public class MyHostServiceClientFactory {

    private final String materialValue;
    private final ServiceBuilder serviceBuilder;

    public MyHostServiceClientFactory(String materialValue, ServiceBuilder serviceBuilder) {
        this.materialValue = materialValue;
        this.serviceBuilder = serviceBuilder;
    }

    public MyHostServiceClient myHostClient(String qualiferOne, boolean useService) {
        if(useService) {
            return serviceBuilder
           .remote(MyHostServiceClient.class)
           .withConfig(qualifierOne)
           .withCall(new CallerAttach(Caller.retro(defaultStrategy())), // Error Line2 Here
              new SigningVisitor(new CredentialsProvider(materialValue))),
              call -> call.doSomeStuff(StuffObject.getStuffInstance()))
           .makeClient();
    }

    // can also be injected as a dependency rather than being hardcoded
    DefaultStrategy<Object> defaultStrategy() {
        final int valueA = 1;
        final int valueB = 2;
        return new DoSomeThingsBuilder()
            .retry(valueA)
            .doSomethingElse(valueB)
            .create();
    }
}

Upvotes: 1

Related Questions