How to test service class with #Autowired - spring

I have a service class MyService which is defined and being used in controller like so:
public interface MyService {
public String someMethod()
}
#Service("myService")
public class MyServiceImpl implements MyService {
public String someMethod() {
return "something";
}
}
#Controller
public class MyController {
#Autowired
public MyService myService;
#RequestMapping(value="/someurl", method=RequestMethod.GET)
public String blah () {
return myService.getsomeMethod();
}
}
I'd like to write a test case for the someMethod method, however, the following doesn't work. How can I wire in the implementation class?
public class MyServiceImplTest {
#Autowired
private MyService myService;
#Test
public void testSomeMethod() {
assertEquals("something", myService.someMethod());
}
}

public class MyServiceImplTest {
private MyService myService = new MyServiceImpl();
#Test
public void testSomeMethod() {
assertEquals("something", myService.someMethod());
}
}
Why inject the bean in your test rather than creating an instance by yourself?

Try this:
#RunWith(SpringJUnit4ClassRunner.class)
// specifies the Spring configuration to load for this test fixture
#ContextConfiguration("yourapplication-config.xml")
Also see the Spring.IO docs for more detail.

Related

Spring-Boot Generic Constructor Injection Custom

public abstract class BaseController<E,DTO> {
protected final BaseService<E,DTO> service;
public BaseController(final BaseService<E,DTO> service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto> {
public CarController(final CarService service) {
super(service);
}
// code ...
}
#Service
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
}
Spring-boot generic method develop. Service created in constructor. There is no problem working properly. Problem is custom method of service. Because service extends BaseService and see all base services methods but, it is custom services as CarService. Intellij give error
service.getname();
how can i solve this problem
your service is type of BaseService, not CarService.
When you write something like BaseService service = new CarService() you can't access methods of CarService because your variable is BaseService type.
you would need to change service to be type of generic, same as you did with entity and dto.
public abstract class BaseController<E,DTO, S extends BaseService> {
protected final S service;
public BaseController(S service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
}
// code ...
}
The extends BaseService in BaseController is only required if you would like to use any methods of BaseService in BaseController, like for example:
public abstract class BaseController<E,DTO, S extends BaseService> {
protected final S service;
public BaseController(S service) {
this.service = service;
service.doSomething();
service.overrideMe();
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
service.overrideMe();
service.doSomething();
}
// code ...
}
public abstract class BaseService<E, DTO> {
public abstract void overrideMe();
public void doSomething() {
System.out.println("hi");
}
}
#Service
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
#Override
public void overrideMe() {
System.out.println("Overrided");
}
}
If your BaseController doesn't need to know that S is extending BaseService (you won't call any methods of BaseService in BaseController) you can delete extends BaseService part
public abstract class BaseController<E,DTO, S> {
protected final S service;
public BaseController(S service) {
this.service = service;
}
// code ....
}
public class CarController extends BaseController<Car, CarDto, CarService> {
public CarController(final CarService service) {
super(service);
service.getName();
service.overrideMe();
service.doSomething();
}
// code ...
}
public abstract class BaseService<E, DTO> {
public abstract void overrideMe();
public void doSomething() {
System.out.println("hi");
}
}
public class CarService extends BaseService<Car, CarDto> {
// code ....
//custom method
public String getName(){
return "Car Sevice";
}
#Override
public void overrideMe() {
System.out.println("Overrided");
}
}
Also think if you need E and DTO in BaseController

Mocking a service in a Testcontainer Spring boot test

I am quite new in Spring and I am facing an issue right now with testing:
I have the following Service:
#Service
public class MyService {
public Integer getKey() {
List<Integer> keys = getKeys(1);
if (keys.size() == 1) {
return keys.get(0);
}
throw new IllegalArgumentException("Error!");
}
... and a getKeys() method, which provides a list based ona rest call...
}
And I use this service class in antother class:
#NoArgsConstructor
public class MyOtherClass extends MyClass {
#Autowired
private MyService myService;
....
#Override public KeyValue<Object, Object> doSomething(Object key, Object value) {
if (conditionIsTrue(key, value)) {
MyObject obj = new MyObject();
myObject.setKey(keyService.getKey()); ----- here is always null the keyService
.....
} else {
return KeyValue.pair(null, null);
}
}
And I try to write a test but the MyService is always null..
#ActiveProfiles("my-test")
#SpringBootTest(classes = Application.class)
#Testcontainers
#Slf4j
public class MyTest extends TestContext {
#BeforeEach
void init(final TestInfo testInfo) {
....
}
#AfterEach
void deinit() {
....
}
#Test
public void myTest() {
....
}
How can I inject a mock MyService into the test container?
Thank you!

How refactor 2 restcontrollers almost identical?

I have 2 rest controllers which look the same:
File 1:
#RestController
#RequestMapping("/api/v1/foo")
#RequiredArgsConstructor
public class FooAPI {
private final ConfigService<Foo> service;
#GetMapping
(...)
File 2:
#RestController
#RequestMapping("/api/v1/bar")
#RequiredArgsConstructor
public class BarAPI {
private final ConfigService<Bar> service;
#GetMapping
(...)
Everything is the same, except the private final ConfigService<(Foo|Bar)> service;.
Is it possible to generalize a restcontroller class?
You can create abstract class that you can inherit from.
So you get something like:
Abstract class:
public abstract class AbstractAPI {
private final ConfigService<?> service;
public AbstractAPI(ConfigService<?> service){
this.service = service;
}
#GetMapping
(...)
File 1:
#RestController
#RequestMapping("/api/v1/foo")
public class FooAPI extends AbstractAPI {
public FooAPI (ConfigService<?> service) {
super(service);
}
}
File 2:
#RestController
#RequestMapping("/api/v1/bar")
public class BarAPI extends AbstractAPI {
public BarAPI (ConfigService<?> service) {
super(service);
}
}

Spring boot autowiring an interface with multiple implementations

In normal Spring, when we want to autowire an interface, we define it's implementation in Spring context file.
What about Spring boot?
how can we achieve this?
currently we only autowire classes that are not interfaces.
Another part of this question is about using a class in a Junit class inside a Spring boot project.
If we want to use a CalendarUtil for example, if we autowire CalendarUtil, it will throw a null pointer exception. What can we do in this case? I just initialized using "new" for now...
Use #Qualifier annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
public interface MyService {
void doWork();
}
#Service
#Qualifier("firstService")
public static class FirstServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("firstService work");
}
}
#Service
#Qualifier("secondService")
public static class SecondServiceImpl implements MyService {
#Override
public void doWork() {
System.out.println("secondService work");
}
}
#Component
public static class FirstManager {
private final MyService myService;
#Autowired // inject FirstServiceImpl
public FirstManager(#Qualifier("firstService") MyService myService) {
this.myService = myService;
}
#PostConstruct
public void startWork() {
System.out.println("firstManager start work");
myService.doWork();
}
}
#Component
public static class SecondManager {
private final List<MyService> myServices;
#Autowired // inject MyService all implementations
public SecondManager(List<MyService> myServices) {
this.myServices = myServices;
}
#PostConstruct
public void startWork() {
System.out.println("secondManager start work");
myServices.forEach(MyService::doWork);
}
}
}
For the second part of your question, take look at this useful answers first / second
You can also make it work by giving it the name of the implementation.
Eg:
#Autowired
MyService firstService;
#Autowired
MyService secondService;
Assume that you have a GreetingService
public interface GreetingService {
void doGreetings();
}
And you have 2 implementations HelloService
#Service
#Slf4j
public class HelloService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hello world!");
}
}
and HiService
#Slf4j
#Service
public class HiService implements GreetingService{
#Override
public void doGreetings() {
log.info("Hi world!");
}
}
Then you have another interface, which is BusinessService to call some business
public interface BusinessService {
void doGreetings();
}
There are some ways to do that
#1. Use #Autowired
#Component
public class BusinessServiceImpl implements BusinessService{
#Autowired
private GreetingService hiService; // Spring automatically maps the name for you, if you don't want to change it.
#Autowired
private GreetingService helloService;
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
In case you need to change your implementation bean name, refer to other answers, by setting the name to your bean, for example #Service("myCustomName") and applying #Qualifier("myCustomName")
#2. You can also use constructor injection
#Component
public class BusinessServiceImpl implements BusinessService {
private final GreetingService hiService;
private final GreetingService helloService;
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService) {
this.hiService = hiService;
this.helloService = helloService;
}
#Override
public void doGreetings() {
hiService.doGreetings();
helloService.doGreetings();
}
}
This can be
public BusinessServiceImpl(#Qualifier("hiService") GreetingService hiService, #Qualifier("helloService") GreetingService helloService)
But I am using Spring Boot 2.6.5 and
public BusinessServiceImpl(GreetingService hiService, GreetingService helloService)
is working fine, since Spring automatically get the names for us.
#3. You can also use Map for this
#Component
#RequiredArgsConstructor
public class BusinessServiceImpl implements BusinessService {
private final Map<String, GreetingService> servicesMap; // Spring automatically get the bean name as key
#Override
public void doGreetings() {
servicesMap.get("hiService").doGreetings();
servicesMap.get("helloService").doGreetings();
}
}
List also works fine if you run all the services. But there is a case that you want to get some specific implementation, you need to define a name for it or something like that. My reference is here
For this one, I use #RequiredArgsConstructor from Lombok.
As mentioned in the comments, by using the #Qualifier annotation, you can distinguish different implementations as described in the docs.
For testing, you can use also do the same. For example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyClassTests {
#Autowired
private MyClass testClass;
#MockBean
#Qualifier("default")
private MyImplementation defaultImpl;
#Test
public void givenMultipleImpl_whenAutowiring_thenReturnDefaultImpl() {
// your test here....
}
}
There are 2 approaches when we have autowiring of an interface with multiple implementations:
Spring #Primary annotation
In short it tells to our Spring application whenever we try to autowire our interface to use that specific implementation which is marked with the #Primary annotation. It is like a default autowiring setting. It can be used only once per cluster of implementations of an interface. → #Primary Docs
Spring #Qualifier annotation
This Spring annotation is giving us more control to select the exact implementation wherever we define a reference to our interface choosing among its options. → #Qualifier Docs
For more details follow the links to their documentation.
public interface SomeInterfaces {
void send(String message);
String getType();
}
kafka-service
#Component
public class SomeInterfacesKafkaImpl implements SomeInterfaces {
private final String type = "kafka";
#Override
public void send(String message) {
System.out.println(message + "through Kafka");
}
#Override
public String getType() {
return this.type;
}
}
redis-service
#Component
public class SomeInterfacesRedisImpl implements SomeInterfaces {
private final String type = "redis";
#Override
public void send(String message) {
System.out.println(message + "through Redis");
}
#Override
public String getType() {
return this.type;
}
}
master
#Component
public class SomeInterfacesMaster {
private final Set<SomeInterfaces> someInterfaces;
public SomeInterfacesMaster(Set<SomeInterfaces> someInterfaces) {
this.someInterfaces = someInterfaces;
}
public void sendMaster(String type){
Optional<SomeInterfaces> service =
someInterfaces
.stream()
.filter(service ->
service.getType().equals(type)
)
.findFirst();
SomeInterfaces someService =
service
.orElseThrow(() -> new RuntimeException("There is not such way for sending messages."));
someService .send(" Hello. It is a letter to ....");
}
}
test
#SpringBootTest
public class MultiImplementation {
}
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTest extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMaster() {
someInterfacesMaster.sendMaster("kafka");
}
}
Thus, according to the Open/Closed principle, we only need to add an implementation without breaking existing code.
#Component
public class SomeInterfacesRabbitImpl implements SomeInterfaces {
private final String type = "rabbit";
#Override
public void send(String message) {
System.out.println(message + "through Rabbit");
}
#Override
public String getType() {
return this.type;
}
}
test-v2
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeInterfacesMasterTestV2 extends MultiImplementation {
#Autowired
private SomeInterfacesMaster someInterfacesMaster;
#Test
void sendMasterV2() {
someInterfacesMaster.sendMaster("rabbit");
}
}
If we have multiple implementations of the same interface, Spring needs to know which one it should be autowired into a class. Here is a simple example of validator for mobile number and email address of Employee:-
Employee Class:
public class Employee {
private String mobileNumber;
private String emailAddress;
...
/** Getters & Setters omitted **/
}
Interface EmployeeValidator:
public interface EmployeeValidator {
public Employee validate(Employee employee);
}
First implementation class for Mobile Number Validator:
#Component(value="EmployeeMobileValidator")
public class EmployeeMobileValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Mobile number Validation logic goes here.
}
}
Second implementation class for Email address Validator:
#Component(value="EmployeeEmailValidator")
public class EmployeeEmailValidator implements EmployeeValidator {
#Override
public Employee validate(Employee employee) {
//Email address validation logic goes here.
}
}
We can now autowired these above validators individually into a class.
Employee Service Interface:
public interface EmployeeService {
public void handleEmployee(Employee employee);
}
Employee Service Implementation Class
#Service
public class EmployeeServiceImpl implements EmployeeService {
/** Autowire validators individually **/
#Autowired
#Qualifier("EmployeeMobileValidator") // Autowired using qualifier for mobile validator
private EmployeeValidator mobileValidator;
#Autowired
#Qualifier("EmployeeEmailValidator") // Autowired using qualifier for email valodator
private EmployeeValidator emailValidator;
#Override
public void handleEmployee(Employee employee) {
/**You can use just one instance if you need**/
employee = mobileValidator.validate(employee);
}
}

Null Pointer when using #SpringBootTest

I am using spring boot 1.4,
when using the #SpringBootTest annotation for integration test, it gives a null pointer.
#RunWith(SpringRunner.class);
#SpringBootTest
public class MyControllerTest {
#Test
public void mytest {
when().
get("/hello").
then().
body("hello");
}
}
and for main class:
#SpringApplication
#EnableCaching
#EnableAsync
public class HelloApp extends AsyncConfigureSupport {
public static void main(String[] args) {
SpringApplication.run(HelloApp.class, args);
}
#Override
public Executor getAsyncExecutor() {
...
}
}
Then in my controller:
#RestController
public class HelloController {
#Autowired
private HelloService helloService;
#RequestMapping("/hello");
public String hello() {
return helloService.sayHello();
}
}
HelloService
#Service
public class HelloService {
public String sayHello() {
return "hello";
}
}
But it ways says NullPointException when for helloService when processing request.
What am I missing?
You need to mock HelloService in your test class as your controller is calling a service .Here in your case Your Test class is not aware that there is any service available or not
The following example test class might help you. In this guide from spring an example is shown how to integration test a rest controller in a spring fashion way.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class HelloControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void hello() throws Exception {
mockMvc.perform(get("/hello")).andExpect(content().string("hello"));
}
}

Resources