2018-10-24 14:19:45 +00:00
|
|
|
# Adding tests to the project
|
2018-10-24 14:12:00 +00:00
|
|
|
|
|
|
|
This document explains a bit about our testing infrastructure, and how you can
|
|
|
|
contribute tests to go with changes to the codebase.
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
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.
|
2018-10-24 14:19:31 +00:00
|
|
|
- the subdirectories defined here are intended to match the layout of the
|
2018-10-24 14:12:00 +00:00
|
|
|
`app/src/` directory, but this has not been rigorously defined and will be
|
|
|
|
affected by our plans in [#5645](https://github.com/desktop/desktop/pull/5645)
|
|
|
|
to evolve the source layout
|
2018-10-24 14:19:31 +00:00
|
|
|
- `integration` - these tests are for end-to-end testing and involve launching
|
|
|
|
and driving the app using UI automation
|
2018-10-24 14:12:00 +00:00
|
|
|
|
|
|
|
Other important folders:
|
|
|
|
|
|
|
|
- `fixtures` - this folder contains Git repositories which can be used in tests
|
|
|
|
- `helpers` - modules containing logic to help setup, manage and teardown tests
|
|
|
|
- `__mocks__` - this is a special Jest folder that contains mocks for Electron
|
|
|
|
APIs, which are only stubbed out when tests require it
|
|
|
|
|
|
|
|
## Adding Unit Tests
|
|
|
|
|
|
|
|
Unit 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.
|
|
|
|
|
2019-12-23 05:46:07 +00:00
|
|
|
First, check under `app/test/unit` for an existing test module related to the
|
2018-10-24 14:12:00 +00:00
|
|
|
area you are working in - we want to ensure each test module corresponds to a
|
|
|
|
specific application module.
|
|
|
|
|
|
|
|
### Creating a new test 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:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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 Jest runner successfully.
|
|
|
|
|
|
|
|
Once you're happy that the test is being run, feel free to write some proper
|
|
|
|
tests to exercise your work.
|
|
|
|
|
|
|
|
### Adding tests to a test module
|
|
|
|
|
2018-12-27 17:48:46 +00:00
|
|
|
Feel free to borrow ideas from our [current test suite](https://github.com/desktop/desktop/tree/development/app/test/unit),
|
2018-10-24 14:19:31 +00:00
|
|
|
but here are some guidelines to help you figure out what to test.
|
2018-10-24 14:12:00 +00:00
|
|
|
|
|
|
|
- focus on a specific module or function when writing unit tests - complex unit
|
2018-11-14 14:44:54 +00:00
|
|
|
tests are a sign that the code isn't organized in an ideal way for testing,
|
2018-10-24 14:19:31 +00:00
|
|
|
or that the test is doing too much
|
|
|
|
- write tests to cover the scenarios you think we should care about in the
|
|
|
|
long-term (these are great to help us not accidentally regress your work)
|
2018-10-24 14:12:00 +00:00
|
|
|
- keep tests simple and easy to read - code comments shouldn't be necessary
|
|
|
|
- if you're not confident in writing tests, the [Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAssert)
|
|
|
|
pattern is a nice way to get started
|
|
|
|
|
|
|
|
As you're writing your tests, don't forget to `yarn test:unit` to verify that
|
|
|
|
your tests are working as expected.
|
|
|
|
|
2018-11-14 14:44:54 +00:00
|
|
|
## Specific Testing Scenarios
|
|
|
|
|
|
|
|
### State updates in `AppStore`
|
|
|
|
|
|
|
|
If 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:
|
|
|
|
|
|
|
|
- by extracting it from `AppStore`, we can write a pure function that avoids
|
|
|
|
any implicit state and clearly declares what it needs as parameters
|
|
|
|
- by following a pattern of writing functions that take the current state and
|
|
|
|
generate new state, we keep each function focused on a particular task
|
|
|
|
- because the function only depends on the arguments it receives, this becomes
|
|
|
|
much easier to test
|
|
|
|
|
|
|
|
An example of this is `updateChangedFiles` in
|
|
|
|
[`app/src/lib/stores/updates/changes-state.ts`](https://github.com/desktop/desktop/blob/15294ad41016e2fe393ffe942d48ca36cec144e5/app/src/lib/stores/updates/changes-state.ts#L22)
|
|
|
|
which updates the repository state to ensure selection state is remembered
|
|
|
|
correctly.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
export function updateChangedFiles(
|
|
|
|
state: IChangesState,
|
|
|
|
status: IStatusResult,
|
|
|
|
clearPartialState: boolean
|
|
|
|
): ChangedFilesResult {
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This uses the current `IChangesState`, as well as additional parameters and
|
2018-11-23 12:00:21 +00:00
|
|
|
returns an object containing the changes that should be applied to create a new
|
2018-11-14 14:44:54 +00:00
|
|
|
`IChangesState` for the repository:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
/**
|
|
|
|
* Internal shape of the return value from this response because the compiler
|
2019-12-23 05:46:07 +00:00
|
|
|
* seems to complain about attempts to create an object which satisfies the
|
2018-11-14 14:44:54 +00:00
|
|
|
* 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:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
this.repositoryStateCache.updateChangesState(repository, state =>
|
|
|
|
updateChangedFiles(state, status, clearPartialState)
|
|
|
|
)
|
|
|
|
```
|