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. |
If you are looking for a guide on how to manually test Pull Requests / code, see Testing DSpace 7 Pull Requests. |
Technologies used
[src]/.github/workflows/build.yml
These three modules encompass most of the current test framework:
context.turnOffAuthorisationSystem()
" sparingly, and always follow-up with a "context.restoreAuthSystemState()
" as soon as possible (and in the same test method). As turning off the authorization system can affect the behavior of tests, only use these methods when you need to create or delete test data.See the README in our codebase: https://github.com/DSpace/DSpace/#running-tests
A few quick guidelines on writing DSpace Integration Tests
org.dspace.app.rest.test.AbstractControllerIntegrationTest
: This style of Integration Test is for testing any class which is an @Controller
(which is the majority of classes in the REST API / Server Webapp). This type of integration test uses Spring's built in caching and MockMVC
to speed up the integration tests. One example is the ItemRestRepositoryIT (which tests the "/api/core/items" endpoints in the REST API).org.dspace.app.rest.test.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). One example is OAIpmhIT (which tests the dspace-oai module)A few quick guidelines on writing DSpace Unit Tests
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 random or odd CI failures. These odd failures in CI builds may occur anytime the CI environment 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. Keep in mind, JUnit has no defined order of execution for tests. So, if you are seeing tests succeed on your system, but fail in CI (or another system), then it's almost certainly because tests are running in a different order on the two systems...and one order is succeeding while another is failing (likely cause of test data not being cleaned up in prior tests).
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.
Use Builders for automatic cleanup: Whenever possible, use the Builder test classes (see org.dspace.app.rest.builder.*
) in "dpace-server-webapp", as these Builder classes automatically cleanup after themselves!
// Example of creating a test Item via the ItemBuilder class // As soon as the method using this "testItem" completes, the "testItem" will be automatically deleted // by the AbstractBuilderCleanupUtil (which is called @After every test) context.turnOffAuthorisationSystem(); Item testItem = ItemBuilder.createItem(context, collection); context.restoreAuthSystemState(); |
Cleanup after test POST or file upload: 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 it as a UUID, and save to "idRef" local 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 neither of the above are possible) "Manually" delete created data: If you are creating test data in either the "dspace-api" or "dspace-server-webapp", you should use the Builder method (see above). This manual test cleanup may no longer be necessary (in v7.x and above) & should be avoided wherever possible. That said, here's an example of manual cleanup:
// 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(); |
The DSpace ConfigurationService makes this very easy to do!
All you have to do is call setProperty
in your test to change the value to whatever value your test needs or expects, for example:
// Change the value of the "dspace.ui.url" to be "http://mydspace.edu" // NOTE: there are no special permissions required to change values in tests. So, all you need is this one line. configurationService.setProperty("dspace.ui.url", "http://mydspace.edu"); // Any tests after the above call will see "dspace.ui.url = http://mydspace.edu" // NOTE: once the test method completes, our test environment will automatically reset "dspace.ui.url" back to the default value. |
NOTE: You do NOT need to reset the property value back to the default setting. After every test runs, the ConfigurationService reloads the defaults from the dspace.cfg
and the local.cfg
used by our test environment.
As the frontend is an Angular.io application (which uses Angular CLI), we follow the best practices for Testing. This includes concentrating our effort on Unit tests (specs) over Integration / end-to-end (e2e) tests per this section of the Angular testing guide.
Technologies used
[src]/.github/workflows/build.yml
Test exists in a few key places in the codebase:
/src/app
) alongside the code they test. Their filenames always end with ".spec.ts". For example, the login-page.component.ts
has a corresponding login-page.component.spec.ts
test file./cypress/integration
folder. At this this time we have very few of these.*.component.ts
" file must have a corresponding "*.component.spec.ts
" file, and every "*.service.ts
" file must have a corresponding "*.service.spec.ts
" file.See the README in our codebase: https://github.com/DSpace/dspace-angular/#testing
A few quick guidelines on writing Angular tests using Jasmine:
login-page.component.ts
has a corresponding login-page.component.spec.ts
test file.it("should encounter an expect", () => {
timer(1000).subscribe(() => {
expect(true).toBe(false);
})
})
it("should encounter an expect", (done) => {
timer(1000).subscribe(() => {
expect(true).toBe(false);
done();
})
})
fdescribe
instead of describe
). However, be warned that you must remove that focus before the PR can be merged, as otherwise you'll see a large decrease in code coverage (i.e. all non-focused tests will be ignored).You can insert e.g. console.debug(something...)
statements to see what is happening, but the browser opened by Jasmine will likely close before you can read the output.
instead of yarn test
you could try
yarn test --watch=true OR ng test --source-map=true --watch=true --configuration test --include PATH/TO/THE/TEST/SUITE.spec.ts |
--watch=true
should make it pause so that you can inspect the console log.
A few quick guidelines on writing Angular end-to-end (e2e) tests using Cypress:
/cypress/integration/
directory, per the Angular CLI best practices.docs.cypress.io has great guides & documentation helping you learn more about writing/debugging e2e tests in Cypress.