Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents


Info
titleAutomated tests are now required

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. 


Note
titleManual testing of code is obviously still necessary

If you are looking for a guide on how to manually test Pull Requests / code, see Testing DSpace 7 Pull Requests.

Backend (Java / REST) Code 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 codebase).
  •  Unit Tests must be written for any (public or private) methods/classes which don't require database-level access to test.  For example, 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 and/or Community/Collection Admin (as necessary).
  •  Include tests for known error scenarios & error codes.  If the code throws an exception 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 createdSee guidelines below for "Cleaning up test data".
  •  Use "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.
  •  If a test needs to temporarily modify a configuration property's value (in any *.cfg file), see the guidelines below for "Changing configuration properties in tests"

...

Running Tests

...

locally

See the README in our codebase: https://github.com/DSpace/DSpace/#running-tests

Writing Integration Tests

A few quick guidelines on writing DSpace A few quick guidelines on writing DSpace Integration Tests

  • All integration test classes must end in "IT". For example: "ItemRestRepositoryIT.java" or "StructBuilderIT.java"
  • All integration test classes should extend one of the Abstract classes provided.  These include:
    • For dspace-server-webapp, two Abstract classes exist, based on the type of integration test
      • 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)
    • For dspace-api, all Integration Tests should extend org.dspace.AbstractIntegrationTest
  • As Integration Tests generate a lot of test data, all Integration tests must be sure to follow our guidelines for "Cleaning up test data" (see below).
  • Many example Integration Tests can be found in the dspace-server-webapp (see org.dspace.app.rest.*) and dspace-api modules.  We recommend looking at them for example code, etc.

...

  • All unit test classes must end in "Test". For example: "ItemTest.java" or "DiscoverQueryBuilderTest"
  • For dspace-api, all Unit Tests should extend either AbstractUnitTest or AbstractObjectTest AbstractDSpaceObjectTest.  For example, see ItemTest
  • For dspace-server-webapp, Unit Tests need not extend any class and just need to be named ending with Test.  For example, see DiscoverQueryBuilderTest
  • Many example Unit Tests can be found in the dspace-api, and a few in the dspace-server-webapp.  We recommend looking at them for example code, etc.

...

  •  Unit Tests (i.e. specs) must be written for every Angular Component or Service.  In other words, every "*.component.ts" file must have a corresponding "*.component.spec.ts" file, and every "*.service.ts" file must have a corresponding "*.service.spec.ts" file.
  •  Integration (e2e) Tests are recommended for new features but not yet required.
  •  Include tests for different user types (if behaviors differ per user type). This includes testing as (1) an Anonymous user, (2) an Authenticated User (non-Admin), (3) an Administrator and/or Community/Collection Admin (as necessary).
  •  Include tests for known error scenarios.  For example, tests should validate when errors/warnings are expected to appear, and/or validate when buttons are expected to be enabled/disabled.
  •  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.

Running Tests locally

See the README in our codebase: https://github.com/DSpace/dspace-angular/#testing

Writing Unit (spec) Tests

...

  • Specs are required for all Angular Components, Services and other classes.
  • All specs should be placed in the same directory as the Angular Component which they test. The filename should end in ".spec.ts".  For example, the login-page.component.ts has a corresponding login-page.component.spec.ts test file.
  • As much as possible/reasonable, follow the Angular Testing Guide, which provides detailed example tests. Jasmine also has good documentation/tutorials on learning to write tests.
  • Always mock all providers. Doing so ensures that you're only testing your own code, so a change to the service won't break your test. It also ensures that successive tests can't influence each other by sharing data through an service
  • When testing asynchronous code, verify that your expect is actually executed. The following test will always succeed, because the expect is only executed after the it function has already completed:
    it("should encounter an expect", () => {
    timer(1000).subscribe(() => {
    expect(true).toBe(false);
    })
    })

    The easiest way to fix this would be to use the callback function it provides:
    it("should encounter an expect", (done) => {
    timer(1000).subscribe(() => {
    expect(true).toBe(false);
    done();
    })
    })

    Now the test won't complete until the callback function done() is called, so the test above will fail. Other ways of testing asynchronous code include marbles, and fakeAsync.

    A quick way to verify that your expect is actually being used is to flip it and see if that causes the test to fail.

    Note that this applies to all functions in a test suite, not just it. Here's a commit that fixes a number of these issues in beforeEach and a few in it: 066e6cd.
  • If you are working on debugging specific tests, you can add a "focus" on those tests (e.g. fdescribe instead of describe).  However, be warned that you must remove that focus before 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 

      Code Block
      languagebash
      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.

  • https://angular.io/guide/test-debugging is a guide for debugging Angular unit tests in Karma / Jasmine.
  • https://testing-angular.com/ is an online E-Book which walks through creating Angular unit tests in Jasmine & Angular end-to-end tests in Cypress.

...