If you want to be very convenient for testing against APIs and integrate it into CI to verify each commit, then the IT included with spring boot is definitely the best choice.
Write a test Case quickly
@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@ActiveProfiles({Profiles.ENV_IT})public class DemoIntegrationTest{ @Autowired private FooService fooService; @Test public void test(){ System.out.println("tested"); }}Among them, SpringBootTest defines some configurations when running IT. The above code uses random ports, and of course it can also predefined ports, like this
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = {"server.port=9990"})ActiveProfiles force the use of IT Profile. From a best practice, the addresses of databases or other resource components configured by IT Profile should be isolated from the development or Staging environment. Because when an IT runs through many situations, we need to clear the test data.
You can find that such a Case can be injected with Autowired into any desired Service. This is because spring loads the entire context, which is the same as the actual running environment, including database, cache and other components. If you feel that you do not need all resources during testing, then you can delete the corresponding configuration in the profile. This is a complete operating environment, the only difference is that it will automatically shutdown after the use case is completed.
Test a Rest API
Highly recommend a library to add to gradle
testCompile 'io.rest-assured:rest-assured:3.0.3'
Support JsonPath, which is very useful. Click here for specific documents
@Sql(scripts = "/testdata/users.sql")@Testpublic void test001Login() { String username = "[email protected]"; String password = "demo"; JwtAuthenticationRequest request = new JwtAuthenticationRequest(username, password); Response response = given().contentType(ContentType.JSON).body(request) .when().post("/auth/login").then() .statusCode(HttpStatus.OK.value()) .extract() .response(); assertThat(response.path("token"), is(IsNull.notNullValue())); assertThat(response.path("expiration"), is(IsNull.notNullValue()));} @Sql is used to perform SQL insertion test data before testing. Note that given().body() is passed in a java object JwtAuthenticationRequest, because rest-assured will automatically help you use jackson to serialize the object into a json string. Of course, you can also put the converted json into the body, the effect is the same.
The return result is caught by a Response, and then the data can be obtained using JsonPath for verification. Of course there is another more intuitive way, which can obtain the complete response through response.asString() and then deserialize it into a java object for verification.
At this point, the most basic IT is completed. Adding a step gradle test to Jenkins can achieve testing every time the code is submitted.
Some complicated situations
Mixed data
This is the easiest to happen. A project has many devs, and each dev will write its own IT case, so what if there is an impact between the data. It is easy to understand. For example, in a scenario where a test batch writes, the final verification method is to see if the amount of data written is 10,000 lines. Then another dev wrote other cases and happened to add a new data to this table, which turned into 10w+1 rows, so the case written in batches would not be able to escape.
To prevent this situation, we use each test Class to clear the data. Since it is a class-based operation, you can write a base class to solve it.
@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@ActiveProfiles({Profiles.ENV_IT})public abstract class BaseIntegrationTest { private static JdbcTemplate jdbcTemplate; @Autowired public void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } @Value("${local.server.port}") protected int port; @Before public void setupEnv() { RestAssured.port = port; RestAssured.basePath = "/api"; RestAssured.baseURI = "http://localhost"; RestAssured.config = RestAssured.config().httpClient(HttpClientConfig.httpClientConfig().httpMultipartMode(HttpMultipartMode.BROWSER_COMPATIBLE)); } public void tearDownEnv() { given().contentType(ContentType.JSON) .when().post("/auth/logout"); } @AfterClass public static void cleanDB() throws SQLException { Resource resource = new ClassPathResource("/testdata/CleanDB.sql"); Connection connection = jdbcTemplate.getDataSource().getConnection(); ScriptUtils.executeSqlScript(connection, resource); connection.close(); }}@AfterClass uses jdbcTemplate to execute a CleanDB.sql, which clears all test data in this way.
@ Value("${local.server.port}") also needs to be mentioned, because the port is random, Rest-Assured does not know which port the request is sent to. Here, use @Value to get the current port number and set it to RestAssured.port to solve this problem.
How to process shared data
Running a complete IT may require dozens of Classes and hundreds of methods. So what if some data is required for all cases and needs to be cleared only after all cases have been run? In other words, this kind of data cleaning is not based on class, but on a run at once. For example, initial user data, city library, etc.
We played a clever trick and used flyway
@Configuration@ConditionalOnClass({DataSource.class})public class UpgradeAutoConfiguration { public static final String FLYWAY = "flyway"; @Bean(name = FLYWAY) @Profile({ENV_IT}) public UpgradeService cleanAndUpgradeService(DataSource dataSource) { UpgradeService upgradeService = new FlywayUpgradeService(dataSource); try { upgradeService.cleanAndUpgrade(); } catch (Exception ex) { LOGGER.error("Flyway failed!", ex); } return upgradeService; }} You can see that when the Profile is IT, flyway will drop all tables and execute the upgrade script in turn, thereby creating a complete data table, which is of course empty. In the test path of the project, add a huge version of sql, so that flyway can insert common test data at the end, such as src/test/resources/db/migration/V999.0.1__Insert_Users.sql , perfectly solve various data problems.
summary
Using the built-in test service in Spring boot can quickly verify the API. I don’t have to start the service and then click on the manual page to test my API. I directly communicate with my front-end colleagues the Request format and write a Case to verify it.
Of course, there is also a disadvantage of this method, which is that it is inconvenient to stress test the system. Previously, the company's API test cases were written by Jmeter, which would be much more convenient when doing performance tests.