docs/technical/adding-tests.md
This document explains a bit about our testing infrastructure, and how you can contribute tests to go with changes to the codebase.
The tests we have in the repository are found under app/test and are organized
into these subdirectories:
unit - unit tests for small parts of the codebase. This currently makes up
the majority of our tests.
app/src/ directory, but this has not been rigorously defined and will be
affected by our plans in #5645
to evolve the source layoutintegration - these tests are for end-to-end testing and involve launching
and driving the app using UI automationOther important folders:
fixtures - this folder contains Git repositories which can be used in testshelpers - modules containing logic to help setup, manage and teardown testsUnit tests are ideal for functions that don't depend on the DOM or Electron APIs, so if you are working on some code that will benefit from writing tests here is a guide to getting this working.
First, check under app/test/unit for an existing test module related to the
area you are working in - we want to ensure each test module corresponds to a
specific application module.
If no test module exists, create a new file named [app-module]-test.ts where
[app-module] is the filename of the application module you are working in.
Start with this for the contents of the file:
describe('module being tested', () => {
it('can test some code', () => {
expect(true).toEqual(false)
})
})
If you run yarn test:unit from a shell you should see this error, which
indicates the file is loaded into our test runner successfully.
Once you're happy that the test is being run, feel free to write some proper tests to exercise your work.
Feel free to borrow ideas from our current test suite, but here are some guidelines to help you figure out what to test.
As you're writing your tests, don't forget to yarn test:unit to verify that
your tests are working as expected.
AppStoreIf you find yourself writing complex rules as part of updating application
state, consider whether you can extract the logic to a function that lives
outside of AppStore.
This has some important benefits:
AppStore, we can write a pure function that avoids
any implicit state and clearly declares what it needs as parametersAn example of this is updateChangedFiles in
app/src/lib/stores/updates/changes-state.ts
which updates the repository state to ensure selection state is remembered
correctly.
export function updateChangedFiles(
state: IChangesState,
status: IStatusResult,
clearPartialState: boolean
): ChangedFilesResult {
...
}
This uses the current IChangesState, as well as additional parameters and
returns an object containing the changes that should be applied to create a new
IChangesState for the repository:
/**
* Internal shape of the return value from this response because the compiler
* seems to complain about attempts to create an object which satisfies the
* constraints of Pick<T,K>
*/
type ChangedFilesResult = {
readonly workingDirectory: WorkingDirectoryStatus
readonly selectedFileIDs: string[]
readonly diff: IDiff | null
}
And the return value is merged into the current state:
this.repositoryStateCache.updateChangesState(repository, state =>
updateChangedFiles(state, status, clearPartialState)
)