[JAVA] [Spring boot] I thought about testable code by DI

Introduction

In Spring Framework (boot), how do you implement it without modifying the injection class at all while trying to make it "testable code" which is the merit of DI? I thought about it, and I thought about it myself, so I would like to write about its contents.

Premise

What is Spring? What is DI? I won't talk about it. Here is a nice explanation, so please refer to it. I also participated in the study session mentioned at the beginning, but it was a good study.

What I was wondering

Even if you make the code testable, even if you create a stub class for testing because the class to be injected is not implemented, you have to rewrite at least the injected part when that class is completed. I was thinking.

For example, like this.

@RestController
public class DemoController {

	@Autowired
	DemoServiceStub demoService;
	//DemoService demoService;	//Switch commented out here for testing

	@GetMapping("/echo")
	public String echo() {
		return demoService.getMessage();
	}
}

public class DemoService {
	String getMessage(){
		//Still being implemented
		return "xxx";
	}
}

public class DemoServiceStub {
	String getMessage(){
		return "foo is stub";
	}
}

As I wrote in the comment, I have to switch the injection part between the regular class and the stub class. If you want to test only a specific method, it will not affect you, but after all it feels unpleasant to play with the field.

So, here is what I thought about and fixed.

@RestController
public class DemoController {

	@Autowired
	@Qualifier("demoservice")  //Specify the name of the injection target. Do not switch between regular and stub.
	DemoService demoService;

	@GetMapping("/echo")
	public String echo() {
		return demoService.getMessage();
	}
}

//Make one interface
public interface DemoService {
	String getMessage();
}


@Service(value = "demoserviceWIP")  //Give it a name. Don't wear it with a stub.
public class DemoServiceImpl implements DemoService {
	public String getMessage() {
		//Still being implemented
		return "xxx";
	};
}

@Service(value = "demoservice")  //Give it a name
public class DemoServiceImplStub implements DemoService {
	String getMessage(){
		return "foo is stub";
	}
}

This eliminates the need to tweak the source under test (DemoController in this case) for both legitimate and stub. I feel that this method is also effective when there are multiple classes that I want to implement in the interface. In Spring, if there are multiple types (classes) that register beans, it is not possible to determine which one should be registered, so it seems that name resolution is done in this way. (Rather, it feels like I got a hint for this case from this mechanism.)

review

--Implement using an interface to make the regular class and the stub class common ――I feel that this should be the minimum as a DI in the first place. --Give an alias to the injected class (@Service value attribute) --Resolve the name on the injection side (@Qualifier) --The injection side does not make any changes regardless of the implementation status of the injection side. --Switch the injection name on the injection side

Finally

It is a method that hardly came out even if I googled. It may be too obvious or it may be an anti-pattern. In that case, please point it out.

Recommended Posts

[Spring boot] I thought about testable code by DI
Summary of what I learned about Spring Boot
About DI of Spring ①
First Spring Boot (DI)
About DI of Spring ②
Introduction to Spring Boot ① ~ DI ~
[Swift] I thought about compare
About Spring DI related annotations
Spring Boot application code review points
Write test code in Spring Boot
[MacOS] I was disturbed by ruby when installing Spring Boot CLI
Spring Boot programming with VS Code
I tried GraphQL with Spring Boot
I tried Flyway with Spring Boot
Try using Spring Boot with VS Code
I tried Lazy Initialization with Spring Boot 2.2.0
DI SessionScope Bean in Spring Boot 2 Filter
03. I sent a request from Spring Boot to the zip code search API
About Spring ③
Sample code for DB control by declarative transaction in Spring Boot + Spring Data JPA
Testable code
About designing Spring Boot and unit test environment
Run a Spring Boot project in VS Code
Build Spring Boot project by environment with Gradle
I wanted to gradle spring boot with multi-project
Create Spring Boot environment with Windows + VS Code
◆ Get API created by Spring Boot from React
Spring Boot + Spring Data JPA About multiple table joins
What I was addicted to when developing a Spring Boot application with VS Code