Skip to content

Commit 8bd8743

Browse files
committed
copyedit gui->testing
1 parent 3d91c80 commit 8bd8743

File tree

9 files changed

+471
-314
lines changed

9 files changed

+471
-314
lines changed

doc/Glossary.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ A method of changing the internal parameters of an emulator to mimic the behavio
3232

3333
Slits used on each of the Muon instruments to control the neutron flux to the sample. Each "jaw" pivots on one edge, much like a door on a hinge.
3434

35+
## BDD
36+
37+
Behaviour-driven development. See the [Agile Alliance definition of BDD](https://www.agilealliance.org/glossary/bdd/),
38+
and [how we use BDD for testing in Squish](/client/testing/System-Testing-with-Squish-BDD).
39+
3540
## Block
3641

3742
## Block Archive

doc/client/testing/Adding-Unit-Tests.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Adding tests
22

3-
For more detailed information see [an_introduction_to_unit_testing.rst](An-Introduction-to-Unit-Testing).
3+
:::{seealso}
4+
- [Introduction to unit testing](An-Introduction-to-Unit-Testing)
5+
:::
46

57
It is relatively simple to add unit tests for a plug-in in such a way that maven can run them as part of the build.
68

@@ -9,7 +11,8 @@ Here are the steps required in Eclipse:
911
* Create a new Fragment Project
1012
* File > New > Project... > Plug-in Development > Fragment Project
1113
* Set the project name to `\<the full name of the plug-in to test\>.tests`
12-
* Change the location to the repository rather than the workspace: `xxx\ibex_gui\base\\\<project_name>` (don't forget the project name!!)
14+
* Change the location to the repository rather than the workspace: `xxx\ibex_gui\base\\\<project_name>` (don't
15+
forget the project name!!)
1316
* Click "Next"
1417
* Make sure the Execution Environment points at the correct version of Java (currently JavaSE-11)
1518
* Click the "Browse" button next to "Plug-in ID"
@@ -27,7 +30,8 @@ Here are the steps required in Eclipse:
2730
* The class name **must** end in Test to be picked up by the automated build
2831

2932
* Add tests to the class
30-
* Add `org.junit` and `org.mockito` (if required) to the 'Required Plug-ins', under the Dependencies tab for the manifest
33+
* Add `org.junit` and `org.mockito` (if required) to the 'Required Plug-ins', under the Dependencies tab for the
34+
manifest
3135

3236
* Add the test plug-in to the Maven build by [following these steps](../coding/Adding-a-Plugin-or-Feature-to-Maven-Build)
3337

doc/client/testing/An-Introduction-to-Unit-Testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ An example of using Mockito would be to mock a database wrapper so that a real d
199199
}
200200
```
201201

202-
For more detail on Mockito see [here](Using-Mockito-for-Testing-in-the-GUI).
202+
For more detail on Mockito see [here](Mockito).
203203

204204
## Code coverage
205205

@@ -252,4 +252,4 @@ If Eclipse is not picking up changes when you add tests you may need to change t
252252

253253
### IncompatibleClassChangeError
254254

255-
If the tests are failing because of an IncompatibleClassChangeError error then the solution is to delete the bin and target folders for both the main plug-in and the corresponding test plug-in
255+
If the tests are failing because of an IncompatibleClassChangeError error then the solution is to delete the bin and target folders for both the main plug-in and the corresponding test plug-in

doc/client/testing/Mockito.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Mockito
2+
3+
:::{seealso}
4+
- Read the [guide to testing in IBEX](An-Introduction-to-Unit-Testing) before reading this guide.
5+
- For more detailed information on Mockito, see
6+
[the Mockito homepage](https://site.mockito.org/) and the
7+
[Mockito documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html).
8+
:::
9+
10+
## Test Doubles
11+
12+
Test doubles are objects that stand in for a real object, for the purposes of unit testing. Terminology varies but
13+
there are four usual types that are described:
14+
15+
* **Dummy** - an object that is passed around but not directly used by the method under test
16+
* **Fake** - a working implementation of a class with a simplified internal implementation, for example an in memory
17+
database where the production implementation uses a persistent database
18+
* **Stub** - an object that provides a canned answer to a method call
19+
* **Mock** - fake objects which know about which method calls they receive
20+
21+
See [this article](http://martinfowler.com/articles/mocksArentStubs.html) for more information. Mockito mostly helps
22+
with Stub and Mock doubles.
23+
24+
## Verifying Interactions
25+
26+
To create a mock object using Mockito, in a way which is type-safe and works with generics, use the `@Mock` annotation:
27+
28+
```java
29+
import org.mockito.Mock;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.mockito.junit.MockitoJUnitRunner;
33+
import static org.mockito.Mockito.verify;
34+
35+
import java.util.List;
36+
37+
@RunWith(MockitoJUnitRunner.StrictStubs.class)
38+
public class SomeTest {
39+
@Mock private List<String> mockedList;
40+
41+
@Test
42+
public void myUnitTest() {
43+
// using mock object - it does not throw any "unexpected interaction" exception
44+
mockedList.add("one");
45+
mockedList.clear();
46+
47+
// selective, explicit, highly readable verification
48+
verify(mockedList).add("one");
49+
verify(mockedList).clear();
50+
}
51+
}
52+
```
53+
54+
:::{important}
55+
The test class must be annotated with `@RunWith(MockitoJUnitRunner.StrictStubs.class)` - otherwise the `@Mock`
56+
annotation will not be processed, and the mock object will be `null` during the test.
57+
:::
58+
59+
In the above example, the generic `List<String>` interface is mocked, and has some method calls made on it.
60+
The verify calls replace the usual assert calls in this unit test, and check the method calls were made.
61+
62+
For non-generic classes, it is possible to use an older syntax to create the mock inline:
63+
64+
```java
65+
SomeClass mockedList = mock(SomeClass.class);
66+
```
67+
68+
:::{seealso}
69+
The [Mockito Mock documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mock.html)
70+
contains further details about how to construct mocks for more advanced use-cases.
71+
:::
72+
73+
## Stubbing Method Calls
74+
75+
```java
76+
@Mock private LinkedList<String> mockedList;
77+
78+
@Test
79+
public void myUnitTest() {
80+
// stubbing appears before the actual execution
81+
when(mockedList.get(0)).thenReturn("first");
82+
83+
// the following prints "first“
84+
System.out.println(mockedList.get(0));
85+
86+
// the following prints "null" because get(999) was not stubbed
87+
System.out.println(mockedList.get(999));
88+
}
89+
```
90+
91+
This time the concrete class `LinkedList` is mocked instead of an interface.
92+
The mocked object returns what is asked of it when the method call is made with identical arguments.
93+
94+
:::{seealso}
95+
The [Mockito documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html)
96+
contains detailed documentation about how Mockito can be used to mock method calls for various cases.
97+
:::
98+
99+
## Times Method is Called
100+
101+
Mockito has [several options](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/verification/VerificationMode.html)
102+
for checking how many times a particular method is called:
103+
* `atLeast(int minNumber)` at least this many times
104+
* `atLeastOnce()` at least once
105+
* `atMost(int maxNumber)` at most this many times
106+
* `never()` same as `times(0)`
107+
* `times(int number)` exactly this number of times
108+
109+
The default is `times(1)`.
110+
111+
## Any Methods
112+
113+
When verifying method calls, if the value of an argument is not important, Mockito allows you to check that any object
114+
of a specific type was used as an argument instead.
115+
116+
```java
117+
// The initialisable observer has its update method called once
118+
verify(mockObserver, times(1)).update(value, any(Exception.class), anyBoolean());
119+
```
120+
121+
For common types methods such as `anyString()` are available, otherwise `any(Object.class)` can be used. A null
122+
object will also be matched by using any.
123+
See the [Mockito `ArgumentMatchers`](https://site.mockito.org/javadoc/current/org/mockito/ArgumentMatchers.html)
124+
documentation for more details.
125+
126+
## Capturing Values on Method Calls
127+
128+
If you want to capture the object called in a method, perhaps to check some value, then a captor can be used.
129+
See the code below for an example of how to do this.
130+
131+
```java
132+
@Captor private ArgumentCaptor<Exception> exceptionCaptor;
133+
@Mock private InitialisableObserver<String> mockObserver;
134+
@Mock private Converter<Integer, String> mockConverter;
135+
136+
@Test
137+
public void test_ConvertingObservable_with_conversion_exception() throws ConversionException {
138+
//Arrange
139+
// initObservable is what our ConvertingObservable looks at, and testObservable we can call set methods on
140+
TestableObservable<Integer> testObservable = new TestableObservable<>();
141+
InitialiseOnSubscribeObservable<Integer> initObservable = new InitialiseOnSubscribeObservable<Integer>(testObservable);
142+
143+
// converter with a stub conversion method
144+
when(mockConverter.convert(123)).thenThrow(new ConversionException("conversion exception!"));
145+
146+
// Object we are really testing
147+
ConvertingObservable<Integer, String> convertObservable = new ConvertingObservable<>(initObservable, mockConverter);
148+
149+
//Act
150+
convertObservable.addObserver(mockObserver);
151+
convertObservable.setSource(initObservable);
152+
testObservable.setValue(123);
153+
154+
//Assert
155+
// The initialisable observer has its onError message called once, for the ConversionException
156+
verify(mockObserver, times(0)).onValue(anyString());
157+
verify(mockObserver, times(1)).onError(exceptionCaptor.capture());
158+
assertEquals("conversion exception!", exceptionCaptor.getValue().getMessage());
159+
}
160+
```
161+
162+
:::{important}
163+
As with the `@Mock` annotation, the `@Captor` annotation will only be processed if the test class is annotated with
164+
`@RunWith(MockitoJUnitRunner.StrictStubs.class)`.
165+
:::
166+
167+
## Checking Order of Method Calls
168+
169+
Mockito can be used to check the order methods were called in.
170+
171+
```java
172+
InOrder inOrder = inOrder(firstMock, secondMock);
173+
174+
inOrder.verify(firstMock).add("was called first");
175+
inOrder.verify(secondMock).add("was called second");
176+
```
177+
178+
## Spies
179+
180+
Spies can be used to stub a method or verify calls on a real class. Needing to use a partial mock like this might be
181+
a symptom of problems with code though!
182+
183+
```java
184+
// These are equivalent, but the first is the preferred approach
185+
@Spy Foo spyOnFoo = new Foo("argument");
186+
Foo spyOnFoo = Mockito.spy(new Foo("argument"));
187+
```
188+
189+
## Examples
190+
191+
### IBEX Observable
192+
193+
Below is a full example, showing how the verification and stubbing can be used to check behaviour of an
194+
observable.
195+
196+
In this example, `InitialiseOnSubscribeObservable` takes another observable as its argument, gets the current value of
197+
that observable, and listens for changes. Here we stub the class that `InitialiseOnSubscribeObservable` is observing,
198+
to simplify the test. The only method call we are testing is `getValue()`.
199+
200+
The `InitialisableObserver` is also mocked. As part of the test we want to check that it has its `update()` method
201+
called with a specific set of arguments. We use `times(1)` to specify we want the method called exactly once.
202+
203+
```java
204+
@Mock private InitialisableObserver<String> mockObserver;
205+
@Mock private CachingObservable<String> mockObservable;
206+
207+
@Test
208+
public void test_InitialiseOnSubscribeObservable_subscription() {
209+
//Arrange
210+
String value = "value";
211+
212+
when(mockObservable.getValue()).thenReturn(value);
213+
214+
// Object we are really testing
215+
InitialiseOnSubscribeObservable<String> initObservable =
216+
new InitialiseOnSubscribeObservable<>(mockObservable);
217+
218+
//Act
219+
Object returned = initObservable.addObserver(mockObserver);
220+
221+
//Assert: The initialisable observer has its update method called once
222+
verify(mockObserver, times(1)).update(value, null, false);
223+
224+
// The InitialiseOnSubscribeObservable has the value returned from the mock observable
225+
assertEquals(value, initObservable.getValue());
226+
227+
// A Unsubscriber is returned
228+
assertEquals(Unsubscriber.class, returned.getClass());
229+
}
230+
```
231+
232+
### DB Tests using `Answer`
233+
234+
In [`RdbWritterTests`](https://github.com/ISISComputingGroup/EPICS-IocLogServer/blob/master/LogServer/src/test/java/org/isis/logserver/rdb/RdbWritterTests.java),
235+
there is an example of using an answer to perform a more complicated return. The answer works like this:
236+
237+
```java
238+
when(mockPreparedStatement.executeQuery()).thenAnswer(resultAndStatement.new ResultsSetAnswer());
239+
```
240+
241+
In this case the answer class is implemented as an inner class of another class, but this is not necessary.
242+
The answer looks like:
243+
244+
```java
245+
public class ResultsSetAnswer implements Answer<ResultSet> {
246+
@Override
247+
public ResultSet answer(InvocationOnMock invocation) throws Throwable {
248+
openedResultsSet++;
249+
return resultSet;
250+
}
251+
}
252+
```
253+
254+
In the above example, the `Answer` is used to keep track of the number of times a result set was opened; this `Answer`
255+
implementation makes that information available in its parent class.
256+
257+
## Tips and Advice
258+
259+
* Use mocks to test interactions between a class and a particular interface
260+
* Use mocks to avoid unit tests touching complex or buggy dependencies
261+
* Do not mock type you don't own? Perhaps...
262+
* Do not mock simple classes or value objects - may as well use the real thing
263+
* Do not mock everything!

0 commit comments

Comments
 (0)