Contribute to the DSpace Development Fund
The newly established DSpace Development Fund supports the development of new features prioritized by DSpace Governance. For a list of planned features see the fund wiki page.
As of DSpace 7 (and above), all new code MUST come with corresponding unit or integration tests.
This guide provides an overview of how to write good unit and integration tests both for the Java (REST API) backend and the Angular (UI) frontend.
Backend (Java / REST) Code Tests
Technologies used
- JUnit 4 (for writing tests)
- Mockito (for mocking objects or responses within tests)
- H2 In-Memory Database (for the database backend within test environment)
- Spring Boot Test (for testing the Server Webapp / REST API), which itself uses Hamcrest (matchers), JsonPath (for JSON parsing) and other testing tools.
Overview of Test Framework
These three modules encompass most of the current test framework:
- Parent module (pom.xml): This POM builds our testEnvironment.zip (see "generate-test-env" profile). This test environment consists of a dspace install folder (including all configs and required subdirectories). It is unzipped and used by all other modules for testing. Modules can also override the default test environment configurations by adding their own "/test/data/dspaceFolder"
- dspace-api (Java API): This layer has some basic unit & integration tests for the Java API / DAOs. (Unfortunately, at this time, this module does not have as many tests as it could/should)
- dspace-server-webapp (Server Webapp / REST API): This layer is primarily integration tests for the REST API and other web interfaces (SWORD, OAI-PMH, etc).
- Two types of integration tests exist
AbstractControllerIntegrationTest
: This style of Integration Test is for testing any class which is an@Controller
(which is the majority of classes in the Server Webapp). This type of integration test uses Spring's built in caching andMockMVC
to speed up the integration tests.AbstractWebClientIntegrationTest
: This style of Integration Test is for testing classes which require a full webserver to run. As such, it will run slower, and is primarily used for non-REST API classes, such as testing SWORD or OAI-PMH. Those each require a full webserver to be started, as they are not Spring Controllers (and are not built on Spring Boot).
- Two types of integration tests exist
Checklist for building good tests
- Integration Tests must be written for any methods/classes which require database access to test. We feel integration tests are more important than unit tests (and you'll see this in our test
- Unit Tests must be written for any (public or private) methods/classes which don't require database-level access to test. (e.g. a utility method that parses a string should have a unit test that proves the string parsing works as expected)
- Include tests for different user types to prove access permissions are working. This includes testing as (1) an Anonymous user, (2) an Authenticated User (non-Admin), (3) an Administrator or Community/Collection Admin.
- Include tests for known error scenarios & error codes. If the code throws an error or returns an 4xx response, then a test should prove that is working.
- Bug fix PRs should include a test that reproduces the bug to prove it is fixed. For clarity, it may be useful to provide the test in a separate commit from the bug fix.
- Every test method must cleanup any test data created. See guidelines below for "cleaning up test data".
- Use "
context.turnOffAuthorisationSystem()
" sparingly, and always follow-up with a "context.restoreAuthSystemState()
". As turning off the authorization system can affect the behavior of tests, only use these methods when you need to create/delete test data.
Cleaning up test data
Integration Tests necessarily have to create test data to verify the code they are testing is working properly. But, just as importantly, they must cleanup any test data they create. Integration tests which do not cleanup after themselves often result in odd Travis CI failures. These odd failures in Travis CI may occur anytime Travis CI runs tests in a different order than your local machine and test data from an earlier test directly affects the results of a later test.
Here are three ways to ensure your test data is cleaned up properly in any Integration tests you create. They are roughly prioritized in terms of preference.
Whenever possible, use the "dspace-server-webapp" Builder test classes (see
org.dspace.app.rest.builder.*
), as these Builder classes automatically cleanup after themselves!// Example of creating a test Item via the ItemBuilder class // As soon as the test method using this Item completes, this Item will be automatically deleted // by the AbstractBuilderCleanupUtil (which is called @After every test) context.turnOffAuthorisationSystem(); Item testItem = ItemBuilder.createItem(context, collection); context.restoreAuthSystemState();
If you are testing a
POST
command (or file upload), you MUST cleanup the POSTed data by parsing the ID out of the response and using a Builder class for cleanup. Our best practice is to use the following code logic. Further examples can be found in the codebase.import static com.jayway.jsonpath.JsonPath.read; import java.util.concurrent.atomic.AtomicReference; AtomicReference<UUID> idRef = new AtomicReference<>(); try { // Example of a POST command to create a new Collection while logged in as the user with the given "authToken" getClient(authToken).perform(post("/api/core/collections") ...[various params and data sent via POST]... // Check the POST was successful, which means we created test content & need to clean it up! .andExpect(status().isCreated()) // From the JSON response, read the "id" field, parse as a UUID, and save to "idRef" variable. .andDo(result -> idRef.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); } finally { // Using the CollectionBuilder, delete the Collection with UUID equal to value of idRef. CollectionBuilder.deleteCollection(idRef.get()); }
If you create test data in the "dspace-api", unfortunately, you must manually cleanup after yourself at this time. The "dspace-api" module doesn't have automatic cleanup utilities as described above. Here's an example of doing so:
// Create test data by temporarily turning off authorization context.turnOffAuthorisationSystem(); WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, true); Item item = installItemService.installItem(context, workspaceItem); context.restoreAuthSystemState(); [perform various tests] //Delete the test Item created, again by temporarily turning off authorization context.turnOffAuthorisationSystem(); itemService.delete(context, item); context.restoreAuthSystemState();