开发者

How to design a private/final method available for mocking?

开发者 https://www.devze.com 2023-01-25 08:42 出处:网络
This is the class that I have开发者_如何学运维 to test: public class Downloader { public String download(String uri) {

This is the class that I have开发者_如何学运维 to test:

public class Downloader {
  public String download(String uri) {
    HttpClient client = this.getHttpClient();
    client.setURI(uri);
    return client.get();
  }
  private HttpClient getHttpClient() {
    HttpClient client = new HttpClient();
    // + some config
    return client;
  }
}

Very simple. Now I want to test its behavior when getHttpClient() throws an exception. However, I can't mock this method, since it is private. What is a common practice in such a situation?


I would make the HTTPClient a field of the class that is set up on construction (via a interface). Then you have the ability to create a mock HTTPClient that can throw an exception during the test if you want, e.g.:

public class Downloader {
  private IHTTPClient client;

  public Downloader(IHTTPClient client) {
    this.client = client;
  }

  public String download(String uri) { 
    this.initialiseHttpClient(); 
    client.setURI(uri); 
    return client.get(); 
  } 

  private HttpClient initialiseHttpClient() { 
    // + some config 
  } 
}

Then call the constructor with a real HTTPClient in production code and a Mock in the test code. You may need to create a wrapper for HTTPClient for the real code.


If you're trying to test private methods, I think something's not quite right.

You should be testing your class against its contract. The private methods are implementation-dependent, and so (in a sense) it doesn't matter what they do. You should be checking that your public methods work as expected in both functioning and non-functioning scenarios, and reflect this as appropriate back to the client (in this case, your test class).

You may need to substitute some functionality into your class for test purposes (e.g. substitute in a broken JDBC connection etc.) In that scenario I would investigate mocking and dependency injection.


It does sound a little cheesy but I generally make methods like this public and add conspicuous javadocs saying "this method is exposed public only for testing".

You can also use package-only access by having the xunit/mock etc. in the same package.

I tend to prefer using simple solutions like this as opposed to more complex and hard-to-debug techniques like AOP-style code injection.


You could make getHttpClient() protected and subclass it in the test to return what you want, so you'd have something like this in your tests:

public class TestableDownloader extends Downloader {

  protected HttpClient getHttpClient() {
    throw new Exception();
  }
}

This isn't ideal though, you'd be better having a different design which didn't require you to test private methods (perhaps using dependency injection to provide a factory or something).


Private methods are not supposed to get a unit test. You are only supposed to unit test public methods. How a public method is organized internally does not matter to unit testing. A unit is not equal to a method. It is equal to a behavior that is possibly using more than one method to do its job.

Mocking is also a useless thing to do. If you have to mock something, your method is really integrating functions. Your code is needing refactoring to make it only do one thing and then a wrapper method calls it and the to be mocked object to integrate it.

Unit testing is something that sounds like you should do but in reality is a waste of effort that you are better to use in coding your application. Unit testing is no guarantee of better code quality and maybe it is making it worse because you are not spending enough time on your real code.

0

精彩评论

暂无评论...
验证码 换一张
取 消