It is a solution say when we
do integration test using Hyper SQLDB, if the SQL statement have functions which in-memory DB not
support, we can use Java Language Routines to mimic the logic the specific-DB-function
performs.
An examples is if the specific DB is SQL Server, and the SQL
statement has LEN() function which will return the length of the varchar, in
SQL Server, however, HSQLDB not support LEN() function (only support length()),
and also we will not write a specific SQL for HSQLDB for testing purpose, in
this case, we can use Java Language Routines which is a static method of
a Java class, specified with a fully qualified method name in the routine
definition.
Step 1:
define a Java class and a
static method to mimic the function you need
For this case, I need a method by providing a string and return the length of
string, so the method may look like below:
public class JavaRoutinesForHSql {
public static int stringLength(String chars){
if (chars!=null)
return chars.trim().length();
return 0;
}
}
Step 2: define the routine in sql script
Before the SQL statement is called, create the function as below:
Step 3: you can drop the custom routine if needed:
I agree that having a reference to Services, Repositories and Factories from Entities is NOT good. This is the most accepted opinion. Even though Repositories and Factories are citizens of the Domain layer, they are not part of the problem domain. Sometimes (e.g. in the Wikipedia article on the DDD) the concept of the Domain services is calledPure Fabricationwhich implies that the class (Service) "does not represent a concept in the problem domain". I'd rather refer to Factories and Repositories as Pure Fabrications, because Eric Evans does say something else in his book about the concept of Services:
But when an operation is actually an important domain concept, a SERVICE forms a natural part of a MODEL-DRIVEN DESIGN. Declared in the model as a SERVICE, rather than as a phony object that doesn't actually represent anything, the standalone operation will not mislead anyone.
According to the above-said, sometimes calling a Service from Your Entity might be a sane thing to want. Then, You can use the Double Dispatch approach, so that You won't have to keep a reference to the Service in Your Entity class.
We do not need to inject IUserService into the User model but introduced as a method argument, because normally this method is invoked by upper layer, say UI layer controller, if we user Spring Framework, in controller we can autowire the userService and then call findByLastName method provide the service instance:
@RestController
public UserController {
@Autowired
private UserService userServiceImpl;
// ...
@RequestMapping(...)
private void aMethod(){
// say we have a user instance from Request...
List<User> users = user.findByLastName(userServiceImpl); // here is the magic :)
可能有很多和我一样的读者,在得知DDD如此火爆之后,尝试去读了开山之作《领域驱动设计——软件核心复杂性应对之道》,翻看了几张之后,晦涩的语句,不明所以的专业术语,加上翻译导致的语句流畅性,可以说观看体验并不是很好,特别是对于开发经验不是很多的读者。我总结了一下,为何这本书难以理解:
1. 没有阅读软件设计丛书的习惯,更多人偏向于阅读偏应用层面的书籍,“talk is cheap,show me the code”往往更符合大多数人的习惯。
2. 没有太多的开发经验支撑。没有踩过坑,就不会意识到设计的重要性,无法产生共情。
3. 年代有些久远,这本书写于2004年,书中很多软件设计的反例,在当时是非常流行的,但是在现在已经基本绝迹了。大师之所以为大师,是因为其能跨越时代的限制,预见未来的问题,这也是为什么DDD在十几年前就被提出,却在微服务逐渐流行的现阶段才被大家重视。
interface UserRepository extends JpaRepository<User,String>{
@Query("update user set name=? where id=?")
@Modifying(clearAutomatically = true)
@Transactional
void updateName(String name,String id);
}
历史的车轮在滚滚倒退。本节只关注模型本身,不讨论使用中的一些并发问题,再来聊聊其他的一些最佳实践。
interface UserRepository extends JpaRepository<User,String>{
User findById();//√ 然后已经存在findOne了,只是为了做个对比
User findBy身份证号();//可以接受
User findBy名称();//×
List<权限> find权限ByUserId();//×
}
api层定义了一系列的接口和接口依赖的一些java bean,model层也就是我们的领域层。
这两个模块都会打成jar包,外部服务依赖api,api则由app模块使用rpc框架实现远程调用。
admin和app连接同一个数据源,可以查询出短信邮件记录,admin需要自定义发送短信也是通过rpc调用。
简单介绍完了这个项目后,重点来分析下需求,来看看如何构建包结构。
module desc packageType
api api interfact to publish service jar
app api implementation executable jar
admin admin application executable jar
model entities jar
mvc分层天然将controller,service,model,config层分割开,这符合DDD所推崇的分层架构模式(这个模式在原文中有描述,但我觉得和现在耳熟能详的分层结构没有太大的出入,所以没有放到本文中介绍),
而我们的业务需求也将短信和邮件这两个领域拆分开了。
那么,
到底是mvc应该包含业务包结构呢?
还是说业务包结构包含mvc呢?
mvc高于业务分层
//不够好的分层
aaa.message.admin
config
CommonConfig.java
service
CommonService.java
mail
MailTemplateService.java
MailMessageService.java
sms
SmsTemplateService.java
SmsMessageService.java
web
IndexController.java
mail
MailTemplateController.java
MailMessageController.java
sms
SmsTemplateController.java
SmsMessageController.java
MessageAdminApp.java
业务分层包含mvc
//高内聚的分层aaa.message.admin
config
CommonConfig.java
service
CommonService.java
web
IndexController.java
mail
config
MailConfig.java
service
MailTemplateService.java
MailMessageService.java
web
MailTemplateController.java
MailMessageController.java
sms
config
Smsconfig.java
service
SmsTemplateService.java
SmsMessageService.java
web
SmsTemplateController.java
SmsMessageController.java
MessageAdminApp.java
There are different ways to test your Controller (Web or API Layer) classes in Spring Boot, some provide support to write pureUnit Tests and some others are more intended to be used for Integration Tests. Within this post, I’ll cover the main three test approaches available for Spring: using MockMVC in standalone mode, MockMVC together with SpringRunner, and using SpringBootTest.
Introduction
There are a few different approaches to testing available in Spring Boot. It’s a framework that’s constantly evolving, so more options arise in new versions at the same time that old ones are kept for the sake of backward compatibility. The result: multiple ways of testing the same part of our code, and some unclarity about when to use what. Within this post, I’ll help you understand the different alternatives, the reasons why they are available and when it’s better to use each one.
This article focuses on Controller testing since it’s the most unclear part, where mocking objects is possible at different levels.
The sample application
In summary, it’s just a repository of entities -superheroes- exposed through a REST API. It’s important to list also some particularities of the application to further understand what happens when using the different strategies:
If a superhero can’t be found by their identifier, a NonExistingHeroException is thrown. There is a Spring’s @RestControllerAdvice that will intercept that exception and transform it into a 404 status code – NOT_FOUND.
There is a SuperHeroFilter class that will be used in our HTTP communication to add a header to the HTTP Response: X-SUPERHERO-APP.
Server and Client Side Tests
To start with, we can separate server-side and client-side tests. Server-side tests are the most commonly used: you perform your request and you want to check how the server behaves, how the response is formed, what are the retrieved contents, etc.
The client-side tests are not so common, they are useful when you want to verify that an indirect request has been made: you mock the server behavior and then you call some code (on your side) that indirectly will perform a request to that server. That’s exactly what you want to test, that the request has been made and the contents of your request were made, you don’t care about the response since you mocked it. Too bad there are not many good examples of this. Even if you check the official examples, they are not so helpful (see the Javadoc comments). Anyways, the important idea here is that they can be used when you’re writing a client application and you want to verify the requests from your side to the outside world.
We’ll focus on server-side Tests, which are the ones to verify how the server logic works. In this case, the requests are normally mocked, and you want to check how your server logic reacts. These kind of tests are tightly related to the Controller layer in your application since it’s the part in Spring that takes care of handling the HTTP requests.
Server-Side Tests
If we zoom inside server-side tests, there are two main strategies we can identify in Spring: writing Controller tests using the MockMVC approach, or making use of RestTemplate. The first strategy (MockMVC) should be your preferred one if you want to code a realUnit Test, while RestTemplate should be used if you intend to write an Integration Test. The reason is that with MockMVC we can fine-grain our assertions for the Controller. RestTemplate, on the other hand, will use the Spring’s WebApplicationContext (partly or fully, depends on using the Standalone mode or not). Let’s explain these two strategies in more detail.
Inside-Server Tests
We can test directly our Controller logic without needing a web server to be running. That’s what I call inside-server testing, and it’s closer to the definition of a Unit Test. To make it possible, you need to mock the entire web server behavior, so somehow you’re missing parts to be tested in our application. But don’t worry, because those parts can be perfectly covered with an Integration Test.
Strategy 1: MockMVC in Standalone Mode
In Spring, you can write an inside-server test if you use MockMVC in standalone mode, so you’re not loading any context. Let’s see an example of this.
These are the main parts of this code that we need to focus on:
We use the MockitoJUnitRunnerto run our unit test. This one is provided by Mockito and adds some functionality on top of the built-in JUnit runner: detects that the framework is being used, that here are no unused stubs, and initializes for us all the fields annotated with @Mock, so we don’t need to call Mockito.initMocks()method.
Note how we initialize the mocks: our SuperHeroRepository is mocked as usual with the annotation and, since we need it inside our controller class, we annotate our SuperHeroController instance with @InjectMocks, so it’s initialized and the mocked repository is injected in it.
A JacksonTesterobject is also injected here automatically by using the JacksonTester.initFields()method below. This utility class comes with Spring and it’s initialized using a static method as you can see, so it’s kind of tricky.
In the setup method, which is executed before every test, we need to configure Mockito in Standalone mode and explicitly configure our Controller under test, the Controller Advice and our HTTP Filter. We could add the advice and the filter parts to a base class but, in any case, you can see already the main disadvantage of this approach: any part of your logic that is modeled within ControllerAdvice’s, Filters, etc., needs to be configured here since you don’t have any Spring context that can inject them automatically.
For each test, we use our MockMVC instance to perform all kind of fake requests (GET, POST, etc.) and we receive a MockHttpServletResponse in return. Keep in mind that’s not a real response either, everything is being simulated.
Pay attention to how we can verify our surrounding stuff: in line 60 we check that a request with an unexisting id ends up in a NOT_FOUND code, so the ControllerAdvice is working fine. We also have a test method to verify that the header is present, so our Filter is also doing its work. You can do a quick exercise with this code: remove the part of the standalone setup in which we specified the advice and the filter, and run the test again. As expected, it would fail in that case since there is no context to inject these classes.
For the sake of the educational purpose of this post, I included in this test the Filter and the ControllerAdvice. But we could decide not to do that, and leave the tests that verify the presence of the header and the 404 status code to an integration test (so we remove them from here and from the standalone configuration). If we do that we have a pure version of Unit Test: we are testing only the Controller class logic, without any other interference.
Strategy 2: MockMVC with WebApplicationContext
The second strategy we can use to write Unit Tests for a Controller also involves MockMVC, but in this case we can load a Spring’s WebApplicationContext. Since we’re still using an inside-server strategy, there is no web server deployed in this case though.
// Rest of the class omitted, it's the same implementation as in Standalone mode
}
When compared with the Standalone mode, these are the main differences:
The test is executed with SpringRunner, that’s how the context – or part of it – is initialized. When you run the test, you can see at the beginning of the trace how the context starts loading and the injected beans.
With the @WebMVCTest annotation our MockMVC instance gets autoconfigured and available in the context (so we can autowire it as you see below in the code). Besides, we specify in the annotation the controller class we want to test so only a partial Spring context will be loaded (the controller and its surrounding configuration). The annotation implementation is smart enough to know that our Filter and the Controller Advice should be also injected so, in this case, there is no explicit configuration in the setup() method.
Now the repository is injected in the Spring’s context using @MockBean. We don’t need to make any reference to our controller class apart from the one in the annotation, since the controller will be injected and available.
Bear in mind that the responses we’re verifying are still fake. There is no web server involved in this test either. In any case, it’s a perfectly valid test since we’re checking our logic inside our class and, at the same time, some surrounding actors (SuperHeroExceptionHandler and SuperHeroFilter).
The most important difference is that we didn’t need to explicitly load the surrounding actors, since there is a Spring’s context in place. If we create new filters, new controller advices, or any other actor participating in the request-response process, it will get automatically injected in our test so we don’t need to take care of the manual configuration here. There is no fine-grain control of what to use in our test, but it’s closer to what happens in reality: when we load our application, all this stuff gets automatically injected and configured.
You can see here a small transition to what some people could say it’s an integration test. In this case, we’re testing the filter and the controller advice out-of-the-box without making any reference to them. If we include in the future any other class intervening the request-response flow, it would participate in this test as well. Since more than a class’ behavior is included here, you could classify it as an Integration Testbetween those classes. The line is blurry though: on the other hand, you could argue that there is only one Controller being tested here, but you need the extra configuration provided by those classes to have the full behavior of it.
Outside-Server Tests
When you perform an HTTP request to your application to test it, you’re running what I call an outside-server test. However, even being outside you can inject mocks also in these tests, so you could get something similar to a Unit Test also in this case. For instance, in a classical 3-layered application, you could mock the Service layer and test only your Controller through a web server. But, in practice, this approach is much heavier than a normal Unit Test: you’re loading the entire application context unless you tell Spring not to do so during the test (by excluding configuration or only including what you need).
In Spring you can write outside-server tests for REST controllers using a RestTemplate to perform your requests, or the new TestRestTemplate which includes some useful features for integration testing (ability to include authentication headers and fault tolerance). More specifically, in Spring Boot you can also use the @SpringBootTest annotation, and then you can get some of these beans injected in your context, access to the properties loaded from application.properties, etc., out-of-the-box. It’s an alternative to @ContextConfiguration (Spring) that gives you all the Spring Boot features for your test.
Testing Strategies in Spring Boot can be confusing given the number of features and available shortcuts. Let’s have a look at those strategies as we did for MockMVC.
Strategy 3: SpringBootTest with a MOCK WebEnvironment value
If you use @SpringBootTest or @SpringBootTest(webEnvironment = WebEnvironment.MOCK)you don’t load a real HTTP server. Sounds familiar? It’s a similar approach to the strategy 2 (MockMVC with an application context).
So, in theory we were intending to use @SpringBootTest annotation to write an outside-server test but, when we set the WebEnvironment to MOCK, we’re converting it to an inside-server test. We can’t use a RestTemplate since we don’t have any web server, so we need to keep using MockMVC, which now is getting configured thanks to the extra annotation @AutoconfigureMockMVC. This is the trickiest approach between all the available ones in my opinion, and I personally discourage using it. Better go for Strategy 2 with MockMVC and the context loaded for a specific controller: you’ll be more in control of what you’re testing.
Strategy 4: SpringBootTest with a Real Web Server
When you use @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) or @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) you’re testing with a real HTTP server so, in this case, you need to use a RestTemplate or TestRestTemplate. The difference between using a random port or a defined port is just that in the first case the default port 8080 (or the one you override with the server.port property) won’t be used but replaced with a randomly-assigned port number. This is helpful when you want to run parallel tests, to avoid port clashing.
Let’s have a look at the code and then describe the main characteristics.
We use the SpringRunner to run our test but we annotate it with @SpringBootTest specifying the RANDOM_PORT mode. Just by doing that, we’ll get a web server up and running for our tests.
Note that we’re still mocking the repository layer with @MockBean annotation.
We have a TestRestTemplate bean that we can inject because we’re using @SpringBootTest . It behaves exactly the same as a standard RestTemplate, but has some extra functionalities as seen before.
Our requests are now performed using the template (line 18), same as we were trying to reach an external server.
The assertions change now a little bit since the response we want to verify is now a ResponseEntity instead of a MockHttpServletResponse.
Even though our goal is the same – testing the Controller layer, this test approaches the solution from a totally different angle from the one in Strategy 1 (MockMVC in standalone mode). While in there we were just loading our class and not even the surrounding actors (filter and controller advice), we’re here loading the entire Spring Boot context with the web server included. This approach is the heaviest of all, and the most distant to the concept of a Unit Test.
This strategy is mainly intended for Integration Tests. The idea is that you can still mock beans and replace those in the context but you can verify interactions between different classes in your Spring Boot application, with the web server participating as well. My advice: don’t use this strategy for Unit Tests, you’re making it fat and you may lose control of what you’re testing unitarily. But don’t take me wrong: favor this approach for Integration Tests and always include that testing layer in your application, verifying how your different components work together.
Performance and Context Caching
You may think that the Strategy 1 is much more optimal in performance than others; and that Strategy 4 may perform terribly if you need to load the entire Spring Boot Context every time you run a test. Well, that’s not entirely correct. When you use Spring (including Boot) for testing, the application context will be reused by default during the same Test Suite. That means, in our case, that Strategies 2, 3 and 4 are reusing the Spring context after it’s loaded for the first time.
Be aware that context reuse might cause some side effects if your tests are modifying beans included in the context. If that’s your case, you’ll need to do some tricks with the @DirtiesContextannotation and indicate that you want to reload the context.
Conclusion
As we saw, you have many alternatives to test unitarily your Controllers in Spring Boot. We covered from the lightest to the heaviest one.
It’s time to give you some personal advice about when to use what:
Try always to write Unit Tests for your Controller logic without focusing on any other behavior, so in general go for Strategy 1: use MockMVC in Standalone mode.
If you need to test some surrounding behavior related to the Web Layer, such as filtering, interceptors, authentication, etc., then choose Strategy 4: SpringBootTest with a web server on a random port. But manage it as a separate Integration Test since you’re verifying several parts of your application, and don’t miss the Unit Test for the pure Controller layer if you need it (so try to avoid mixing the test layers and keep them separate).