102

I have a component setup that is essentially a launcher for an application. It is configured like so:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService is annotated with the @Service Spring annotation and is autowired into my launcher class without any issues.

I would like to write some jUnit test cases for MyLauncher, to do so I started a class like this:

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

Can I create a Mock object for MyService and inject it into myLauncher in my test class? I currently don't have a getter or setter in myLauncher as Spring is handling the autowiring. If possible, I'd like to not have to add getters and setters. Can I tell the test case to inject a mock object into the autowired variable using an @Before init method?

If I'm going about this completely wrong, feel free to say that. I'm still new to this. My main goal is to just have some Java code or annotation that puts a mock object in that @Autowired variable without me having to write a setter method or having to use an applicationContext-test.xml file. I would much rather maintain everything for the test cases in the .java file instead of having to maintain a separate application content just for my tests.

I am hoping to use Mockito for the mock objects. In the past I have done this by using org.mockito.Mockito and creating my objects with Mockito.mock(MyClass.class).

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Kyle
  • 14,036
  • 11
  • 46
  • 61

6 Answers6

96

You can absolutely inject mocks on MyLauncher in your test. I am sure if you show what mocking framework you are using someone would be quick to provide an answer. With mockito I would look into using @RunWith(MockitoJUnitRunner.class) and using annotations for myLauncher. It would look something like what is below.

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}
Manuel Quinones
  • 4,196
  • 1
  • 19
  • 19
  • @jpganz18 with junit you normally use that, it's like calling MockitoAnnotations.initMocks before each method – fd8s0 Mar 04 '16 at 09:18
  • Right now, I'm using this runner: @RunWith(SpringRunner.class) Can I substitute the MockitoJUnitRunner? If not, is there any way to inject the Mocks without it? (I'm not really using mock objects. I want to inject the same Beans the application uses.) – MiguelMunoz Feb 14 '18 at 23:20
  • for junit 5 you can use `@ExtendWith(MockitoExtension.class)` instead of `@RunWith(MockitoJUnitRunner.class)` – omerhakanbilici May 27 '22 at 12:58
87

The accepted answer (use MockitoJUnitRunner and @InjectMocks) is great. But if you want something a little more lightweight (no special JUnit runner), and less "magical" (more transparent) especially for occasional use, you could just set the private fields directly using introspection.

If you use Spring, you already have a utility class for this : org.springframework.test.util.ReflectionTestUtils

The use is quite straightforward :

ReflectionTestUtils.setField(myLauncher, "myService", myService);

The first argument is your target bean, the second is the name of the (usually private) field, and the last is the value to inject.

If you don't use Spring, it is quite trivial to implement such a utility method. Here is the code I used before I found this Spring class :

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
Pierre Henry
  • 16,658
  • 22
  • 85
  • 105
  • 2
    Great answer! I had to include https://mvnrepository.com/artifact/org.springframework/spring-test in my pom.xml to get ReflectionTestUtils class. – user674669 Mar 16 '18 at 20:43
  • 1
    I would say it's a terrible advice. There are at least two reasons why: 1. anything requires reflection usually smells bad and points to architecture problems 2. the whole point of dependency injection is to be able to replace injected object easily, so again, using reflection you missed that point – artkoshelev May 07 '18 at 08:02
  • @artkoshelev I agree that for testing purpose and "architectural" concerns, construcotr injecton is cleaner and is to be recommended. It also plays better with java config. But if you want to test some existing beans that use field injection and can't or won't modify them, I would argue that it is better to write a test that uses reflection in the setup than no test at all... Besides if reflection is always a code smell or archiecture problem, than Spring is a code smell, Hibernate is a code smell, and so on... – Pierre Henry May 08 '18 at 23:55
  • "Besides if reflection is always a code smell or archiecture problem, than Spring is a code smell, Hibernate is a code smell, and so on..." i certainly wasn't talking about well-tested and well-known frameworks, but actual application code developers wrote. – artkoshelev May 09 '18 at 06:51
  • I prefer this answer. – Andrei Rînea Oct 10 '18 at 14:40
  • 1
    In my case the existing code doesn't autowire myLauncher instance in Test classes. A new instance of myLauncher is initialized in each test methods. I found the above solution useful. – Vinayak Dornala Dec 04 '18 at 20:23
  • @artkoshelev Cannot agree more, that's why civilized people use constructor injection instead of these annotations from hell. – Malkaviano Sep 17 '19 at 12:49
  • Thank you, this is a great all-around solution. In my case I was locked in to running with SpringJUnit4ClassRunner and having "non-obvious" issues with it. This one-line solution solved my problem in an obvious, readable, and maintainable way – rook218 Dec 27 '22 at 14:50
27

Sometimes you can refactor your @Component to use constructor or setter based injection to setup your testcase (you can and still rely on @Autowired). Now, you can create your test entirely without a mocking framework by implementing test stubs instead (e.g. Martin Fowler's MailServiceStub):

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

This technique especially useful if the test and the class under test is located in the same package because then you can use the default, package-private access modifier to prevent other classes from accessing it. Note that you can still have your production code in src/main/java but your tests in src/main/test directories.


If you like Mockito then you will appreciate the MockitoJUnitRunner. It allows you to do "magic" things like @Manuel showed you:

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

Alternatively, you can use the default JUnit runner and call the MockitoAnnotations.initMocks() in a setUp() method to let Mockito initialize the annotated values. You can find more information in the javadoc of @InjectMocks and in a blog post that I have written.

matsev
  • 32,104
  • 16
  • 121
  • 156
4

I believe in order to have auto-wiring work on your MyLauncher class (for myService), you will need to let Spring initialize it instead of calling the constructor, by auto-wiring myLauncher. Once that is being auto-wired (and myService is also getting auto-wired), Spring (1.4.0 and up) provides a @MockBean annotation you can put in your test. This will replace a matching single beans in context with a mock of that type. You can then further define what mocking you want, in a @Before method.

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

Your MyLauncher class can then remain unmodified, and your MyService bean will be a mock whose methods return values as you defined:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

A couple advantages of this over other methods mentioned is that:

  1. You don't need to manually inject myService.
  2. You don't need use the Mockito runner or rules.
snydergd
  • 503
  • 7
  • 11
3

I'm a new user for Spring. I found a different solution for this. Using reflection and making public necessary fields and assign mock objects.

This is my auth controller and it has some Autowired private properties.

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullanıcıadı veya şifre hatalı");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

And this is my Junit test for AuthController. I'm making public needed private properties and assign mock objects to them and rock :)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

I don't know advantages and disadvantages for this way but this is working. This technic has a little bit more code but these codes can be seperated by different methods etc. There are more good answers for this question but I want to point to different solution. Sorry for my bad english. Have a good java to everybody :)

kodmanyagha
  • 932
  • 12
  • 20
1

Look at this link

Then write your test case as

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}
NullPointerException
  • 3,732
  • 5
  • 28
  • 62
  • 1
    This is the correct way to load the Spring Context!! but i think better you create a test context for your tests.. – Tiarê Balbi May 08 '13 at 02:53
  • This isn't necessarily the correct way, it all depends on what you're trying to accomplish. What if you're setting up hibernate sessions in your app context? Do you really want to hit a real database with a unit test? Some people might say "yes", not realizing that they're creating an integration test and possibly mucking up their data. On the other hand, if you created a test app context and point hibernate to an embedded database then your data is better off, but you're still creating an integration test, not a unit test. – Dan Feb 13 '15 at 00:15
  • This is fo so-called "integration tests" when you want to test your components together with the whole context. It is of course useful, but not the same as unit test where you want to test a single class in isolation. – Pierre Henry Sep 16 '16 at 08:02