8

Currently I try to understand how the @Injectable and @Tested annotations are working. I already did some tests and understood the concept but I didn't get how I can use those annotations in real world applications.

Let's say we are developing a language translator class which depends on a web service. The web service methods are encapsulated in a separate class:

// class to test
public class Translator() {
    private TranslatorWebService webService;

    public String translateEnglishToGerman(String word){
        webService = new TranslatorWebService();
        return webService.performTranslation(word);
    }
}

// dependency
public class TranslatorWebService {
    public String performTranslation(String word){
    // perform API calls    
    return "German Translation";
    }
}

To test the Translator class independently, we would like to mock the TranslatorWebService class. According to my understanding, the test class should look like:

public class TranslatorTest {
    @Tested private Translator tested;
    @Injectable private TranslatorWebService transWebServiceDependency;

    @Test public void translateEnglishToGerman() {
        new Expectations() {{
            transWebServiceDependency.performTranslation("House");
            result = "Haus";
        }};

        System.out.println(tested.translateEnglishToGerman("House"));
    }
}

When I executed this test case for the first time, I expected the result "Haus". At second glance I saw that the line

webService = new TranslatorWebService();

will always override the injected mock instance with a real instance. But how can I avoid this behavior without changing the business logic?

Uvuvwevwevwe
  • 971
  • 14
  • 30
Philipp Waller
  • 463
  • 1
  • 7
  • 13

1 Answers1

9

Good question. The thing to notice about JMockit's (or any other mocking API) support for dependency injection is that it's meant to be used only when the code under test actually relies on the injection of its dependencies.

The example Translator class does not rely on injection for the TranslatorWebService dependency; instead, it obtains it directly through internal instantiation.

So, in a situation like this you can simply mock the dependency:

public class TranslatorTest {
    @Tested Translator tested;
    @Mocked TranslatorWebService transWebServiceDependency;

    @Test public void translateEnglishToGerman() {
        new Expectations() {{
            transWebServiceDependency.performTranslation("House");
            result = "Haus";
        }};

        String translated = tested.translateEnglishToGerman("House");

        assertEquals("Haus", translated);
    }
}
Rogério
  • 16,171
  • 2
  • 50
  • 63
  • Thanks for your answer. I didn't know that `@Mocked` objects will injected as well. According to the documentation only `@Injectable` objects support this feature: `For injection to be performed, the test class must also contain one or more mock fields or mock parameters declared to be @Injectable. Mock fields/parameters annotated only with @Mocked or @Capturing are not considered for injection.` (official docs: http://jmockit.github.io/tutorial/BehaviorBasedTesting.html#tested) But anyway, this solution is working for me. Thank you very much! – Philipp Waller Nov 06 '14 at 09:26
  • 1
    `@Mocked` objects are *not* injected; their classes are mocked. – Rogério Nov 06 '14 at 14:09
  • 1
    Just wanted to point out that in this case, by setting the expectation that the web service returns "Haus" and asserting that that the test subject returns "Haus" we really haven't achieved much of a test. The answer about using @Mocked is correct, but to me it seems more important to make sure that we actually achieve some useful test, so I would propose that the test subject be restructured *or* the test be changed to verify only that the test subject calls out to the web service with the expected parameter. – unigeek Nov 11 '14 at 23:15
  • 3
    @unigeek Indeed. The test should simply verify that `performTranslation("House")` got called, nothing more; with the JMockit API, this would translate to replacing the expectation recording block with an expectation *verification* block: `new Verifications() {{ transWebServiceDependency.performTranslation("House"); }};` – Rogério Nov 12 '14 at 14:37