Testable Spring beans using Lombok RequiredArgsConstructor

cherry blossom against a blue sky

When getting started with Spring, one of the first things to learn about is @Autowired, the magical annotation that provides data from thin air! Tutorials will happily tell you that a property can simply be annotated with @Autowired and it will magically work.

@Component
public class FooService {  
    @Autowired
    private FooFormatter fooFormatter;
}

Thing is, it’s true! The above bean will work, the fooFormatter field will automagically get the correct value, and the application will run great…. until it’s time for testing. Of course, it’s still possible to test, but now we need to set up test scaffolds with @SpringBootTest. We can do better!

Spring Constructors

While @Autowired is a great way to inject dependencies into a class, it can make testing difficult and cumbersome. However, Spring provides a solution to this problem in the form of explicit constructors. By defining a constructor that takes in the necessary dependencies as arguments, we can ensure that the class can be easily tested without relying on Spring’s autowiring magic.

Let’s take a look at an example. Suppose we have a UserService class that requires two dependencies, UserRepository and EmailService.

@Component
public class UserService {

    private UserRepository userRepository;
    private EmailService emailService;

    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }

    public void register(User user) {
        userRepository.save(user);
        emailService.sendWelcomeEmail(user);
    }
}

In the above code, we define an explicit constructor that takes in the UserRepository and EmailService dependencies as arguments. This means that when we create an instance of the UserService class, we must pass in these dependencies explicitly. This makes the class easy to test, as we do not need any special test scaffold.

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @Test
    public void testRegister() {
        User user = new User();
        UserService userService = new UserService(userRepository, emailService);
        userService.register(user);
        verify(userRepository).save(user);
        verify(emailService).sendWelcomeEmail(user);
    }
}

This is great! We have easily testable and modularizable code with no special need to set up an enormous test scaffold. Mockito can easily and quickly test our beans. Unfortunately, we have bloomed the amazing @Autowired keyword into having three additional copies of each field; a whole lot of boilerplate.

Enter Lombok

Lombok is a popular open-source Java library that specifically aims to reduce the boilerplate. What we’re looking at here is the @RequiredArgsConstructor annotation. This annotation generates a constructor method with the required parameters, which is exactly what we needed to do, manually, before. Let’s have a look at it in action.

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final EmailService emailService;

    public void register(User user) {
        userRepository.save(user);
        emailService.sendWelcomeEmail(user);
    }
}

In the above code, the UserService class has two dependencies, UserRepository and EmailService. The @RequiredArgsConstructor annotation generates a constructor that takes these two dependencies as arguments. Since the class has no other constructors available, Spring is forced to pick the one with the two arguments, treating them as @Autowire candidates. Much more concise.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.