Search code examples
javajunit5

Mocking a Method with Mockito? but it's an instance inside the called method


So I've been asked at work to add a JUnit test to my code (our app didn't have testing) but I've hit my head against the wall. Essentially I need to test MyDataSourceImpl.Java

  public class MyDataSourceImpl implements MyDataSource {
       @Autowired
       PropertieService propertieService;

       public HttpResponse execute(String url){
              MyHttpClient myClient = new MyHttpClient(url,propertieService.getProperties());
              HttpResponse response = myClient.performCall();
              this.someOtherStuff(response);
              return response;
       }
    }

this is how I run my Test:

@RunWith(MockitoJUnitRunner.class)
public class MyDataSourceImplTest {
      @InjectMocks
      private MyDataSourceImpl myDataSourceImpl;
      @Mock
      private PropertieService propertieService;
      
      @Test
      public void simpleCall(){
           when(propertieService.getProperties()).thenReturn(new HashMap<String,String>());
           HttpResponse response = myDataSourceImpl.execute("https://nothing.com");
           assertEquals(response,"");
      }
}

Now, I can mock the propertieService just fine, but how do I mock the myClient.performCall()? because that tries to call a database and it's never going to work in a Test. I tried treating MyHttpClient like PropertieService and add a @Mock and a when thenReturn, that did nothing. is there any way to do this?

edit: when I write that "I tried treating MyHttpCliente like PropertieService" I mean that I added a @Mock private MyHttpClient myClient; to my Test, and added a line like when(myClient.performCall()).thenReturn(new HttpResponse()); to my simpleCall(). and that does nothing, it still tries to execute the real performCall() method.


Solution

  • Mocking of constructors may be possible, but it's a sign that your code isn't structured in the best way possible.

    Imagine if it was:

    public class MyDataSourceImpl implements MyDataSource {
       @Autowired
       MyHttpClient myClient;
    
       public HttpResponse execute(String url) {
           HttpResponse response = myClient.performCall(url);
           this.someOtherStuff(response);
           return response;
       }
    }
    

    One fewer dependencies, and easy to mock. All from moving the url to the performCall method.

    When you can't change MyClient

    Create a level of indirection:

    public interface HttpClientCall {
        HttpResponse performCall(String url)
    }
    
    public final class MyHttpClientCallAdaptor implements HttpClientCall {
    
        private final PropertieService propertieService;
    
        MyHttpClientCallAdaptor(PropertieService propertieService) {
            this.propertieService = propertieService;
        }
    
        @Override
        public HttpResponse performCall(String url) {
            return new MyClient(url, propertieService)
        }
    } 
    

    Now depend on HttpClientCall:

    public class MyDataSourceImpl implements MyDataSource {
       @Autowired
       HttpClientCall myClient;
    
       public HttpResponse execute(String url) {
           HttpResponse response = myClient.performCall(url);
           this.someOtherStuff(response);
           return response;
       }
    }