1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>EasyMock 3.2 Readme</title>
<link rel="stylesheet" href="easymock.css" />
</head>
<body><div class="bodywidth">
<h2>EasyMock 3.2 Readme</h2>
<p>Documentation for release 3.2 (2013-07-11)<br />
© 2001-2013 <a href="http://www.offis.de">OFFIS</a>, <a href="http://tammofreese.de">Tammo Freese</a>, <a href="http://henritremblay.blogspot.fr/">Henri Tremblay</a>.
</p>
<p>
EasyMock is a library that provides an easy way to use Mock Objects for given
interfaces or classes. EasyMock is available under the terms of the <a href="http://www.apache.org/licenses/LICENSE-2.0.txt">Apache 2 license</a>.
</p>
<p>
Mock Objects simulate parts of the behavior of domain code,
and are able to check whether they are used as defined.
Domain classes can be tested in isolation
by simulating their collaborators with Mock Objects.
</p>
<p>
Writing and maintaining Mock Objects often is a tedious
task that may introduce errors. EasyMock generates Mock Objects
dynamically - no need to write them, and no generated code!
</p>
<h2>
EasyMock Benefits
</h2>
<ul>
<li>Hand-writing classes for Mock Objects is not needed.
</li>
<li>Supports refactoring-safe Mock Objects: test code will not break at runtime when renaming methods or reordering method parameters
</li>
<li>Supports return values and exceptions.
</li>
<li>Supports checking the order of method calls, for one or more Mock Objects.
</li>
</ul>
<h2>
Requirements
</h2>
<ul>
<li>EasyMock only works with Java 1.5.0 and above.</li>
<li>cglib (2.2) and Objenesis (1.2) must be in the classpath to perform class mocking</li>
</ul>
<h2>
Installation
</h2>
<h3>Using Maven</h3>
EasyMock is available in the Maven central repository. Just add the following dependency to your pom.xml:
<pre>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.2</version>
<scope>test</scope>
</dependency>
</pre>
You can obviously use any other dependency tool compatible with the Maven repository.
<h3>Manually</h3>
<ul>
<li>Unzip the EasyMock zip file (<code>easymock-3.2.zip</code>).</li>
<li>Go into the <code>easymock-3.2</code> directory.</li>
<li>Add the EasyMock jar file (<code>easymock.jar</code>) to your classpath.</li>
<li>To perform class mocking, also add <a href="http://www.objenesis.org">Objenesis</a> and <a href="http://cglib.sourceforge.net/">Cglib</a> to your classpath.</li>
<li>The tests are in <code>easymock-3.2-tests.jar</code> and can be launched with a JUnit TestRunner
having JUnit 4.7 on top of EasyMock, cglib and Objenesis in your classpath.</li>
<li>The source code of EasyMock is stored in <code>easymock-3.2-sources.jar</code>.</li>
</ul>
<h2>
Usage
</h2>
<p>
Most parts of a software system do not work in isolation, but collaborate
with other parts to get their job done. In a lot of cases, we do not care
about using collaborators in unit testing, as we trust these collaborators.
If we <em>do</em> care about it, Mock Objects help us to test the unit under test
in isolation. Mock Objects replace collaborators of the unit under
test.
</p>
<p>
The following examples use the interface <code>Collaborator</code>:
</p>
<pre>
package org.easymock.samples;
public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
</pre>
<p>
Implementors of this interface are collaborators
(in this case listeners) of a class named <code>ClassUnderTest</code>:
</p>
<pre>
public class ClassUnderTest {
private Collaborator listener;
// ...
public void setListener(Collaborator listener) {
this.listener = listener;
}
public void addDocument(String title, byte[] document) {
// ...
}
public boolean removeDocument(String title) {
// ...
}
public boolean removeDocuments(String[] titles) {
// ...
}
}
</pre>
<p>
The code for both the class and the interface may be found
in the package <code>org.easymock.samples</code> in <code>easymock-3.2-samples.jar</code>
from the EasyMock zip delivery.
</p>
<p>
The following examples assume that you are familiar with the JUnit testing framework.
Although the tests shown here use JUnit 4, you may as well use JUnit 3 or TestNG.
</p>
<h3>
The first Mock Object
</h3>
<p>
We will now build a test case and toy around with it to understand the
functionality of the EasyMock package. <code>easymock-3.2-samples.jar</code>
contains a modified version of this test. Our first test should check
whether the removal of a non-existing document does <strong>not </strong> lead to a notification
of the collaborator. Here is the test without the definition of the
Mock Object:
</p>
<pre>
package org.easymock.samples;
import org.junit.*;
public class ExampleTest {
private ClassUnderTest classUnderTest;
private Collaborator mock;
@Before
public void setUp() {
classUnderTest = new ClassUnderTest();
classUnderTest.setListener(mock);
}
@Test
public void testRemoveNonExistingDocument() {
// This call should not lead to any notification
// of the Mock Object:
classUnderTest.removeDocument("Does not exist");
}
}
</pre>
<p>
For many tests using EasyMock,
we only need a static import of methods of <code>org.easymock.EasyMock</code>.
</p>
<pre>
import static org.easymock.EasyMock.*;
import org.junit.*;
public class ExampleTest {
private ClassUnderTest classUnderTest;
private Collaborator mock;
}
</pre>
<p>
To get a Mock Object, we need to
</p>
<ol>
<li>create a Mock Object for the interface we would like to simulate,
</li>
<li>record the expected behavior, and
</li>
<li>switch the Mock Object to replay state.
</li>
</ol>
<p>
Here is a first example:
</p>
<pre>
@Before
public void setUp() {
mock = createMock(Collaborator.class); // 1
classUnderTest = new ClassUnderTest();
classUnderTest.setListener(mock);
}
@Test
public void testRemoveNonExistingDocument() {
// 2 (we do not expect anything)
replay(mock); // 3
classUnderTest.removeDocument("Does not exist");
}
</pre>
<p>
After activation in step 3, <code>mock</code>
is a Mock Object for the <code>Collaborator</code>
interface that expects no calls. This means that if we change
our <code>ClassUnderTest</code> to call
any of the interface's methods, the Mock Object will throw
an <code>AssertionError</code>:
</p>
<pre>
java.lang.AssertionError:
Unexpected method call documentRemoved("Does not exist"):
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentRemoved(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74)
at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33)
at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24)
...
</pre>
<h3>
Using annotations
</h3>
<p>
Since EasyMock 3.2, it is now possible to create mocks using annotations. This is a nice and shorter way to create your
mocks and inject them to the tested class. Here is the example above, now using annotations:
</p>
<pre>
import static org.easymock.EasyMock.*;
import org.easymock.EasyMockRunner;
import org.easymock.TestSubject;
import org.easymock.Mock;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(EasyMockRunner.class)
public class ExampleTest {
@TestSubject
private ClassUnderTest classUnderTest = new ClassUnderTest(); // 2
@Mock
private Collaborator mock; // 1
@Test
public void testRemoveNonExistingDocument() {
replay(mock);
classUnderTest.removeDocument("Does not exist");
}
}
</pre>
<p>
The <code>mock</code> is instantiated by the runner at step 1. It is then set by the runner, to the <code>listener</code>
field on step 2. The <code>setUp</code> method can be removed since all the initialisation was done by the runner.
</p>
<h3>
Adding Behavior
</h3>
<p>
Let us write a second test. If a document
is added on the class under test, we expect a call to <code>mock.documentAdded()</code>
on the Mock Object with the title of the document as argument:
</p>
<pre>
@Test
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
}
</pre>
<p>
So in the record state (before calling <code>replay</code>),
the Mock Object does <em>not</em> behave like a Mock Object,
but it records method calls. After calling <code>replay</code>,
it behaves like a Mock Object, checking whether the expected
method calls are really done.
</p>
<p>
If <code>classUnderTest.addDocument("New Document", new byte[0])</code>
calls the expected method with a wrong argument, the Mock Object will complain
with an <code>AssertionError</code>:
</p>
<pre>
java.lang.AssertionError:
Unexpected method call documentAdded("Wrong title"):
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
</pre>
<p>
All missed expectations are shown, as well as all fulfilled
expectations for the unexpected call (none in this case). If the method
call is executed too often, the Mock Object complains, too:
</p>
<pre>
java.lang.AssertionError:
Unexpected method call documentAdded("New Document"):
documentAdded("New Document"): expected: 1, actual: 2
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
</pre>
<h3>
Verifying Behavior
</h3>
<p>
There is one error that we have not handled so far: If we specify
behavior, we would like to verify that it is actually used. The current
test would pass if no method on the Mock Object is called. To verify that the
specified behavior has been used, we have to call
<code>verify(mock)</code>:
</p>
<pre>
@Test
public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
verify(mock);
}
</pre>
<p>
If the method is not called on the Mock Object, we now get the
following exception:
</p>
<pre>
java.lang.AssertionError:
Expectation failure on verify:
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:31)
...
</pre>
<p>
The message of the exception lists all missed expectations.
</p>
<h3>
Expecting an Explicit Number of Calls
</h3>
<p>
Up to now, our test has only considered a single method call. The next
test should check whether the addition of an already existing
document leads to a call to <code>mock.documentChanged()</code>
with the appropriate argument. To be sure, we check this three
times (hey, it is an example ;-)):
</p>
<pre>
@Test
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
</pre>
<p>
To avoid the repetition of <code>mock.documentChanged("Document")</code>,
EasyMock provides a shortcut. We may specify the call count with the method
<code>times(int times)</code> on the object returned by
<code>expectLastCall()</code>. The code then looks like:
</p>
<pre>
@Test
public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}
</pre>
<p>
If the method is called too often, we get an exception that
tells us that the method has been called too many times.
The failure occurs immediately at the first method call
exceeding the limit:
</p>
<pre>
java.lang.AssertionError:
Unexpected method call documentChanged("Document"):
documentChanged("Document"): expected: 3, actual: 4
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentChanged(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged(ClassUnderTest.java:67)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:26)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...
</pre>
<p>
If there are too few calls, <code>verify(mock)</code>
throws an <code>AssertionError</code>:
</p>
<pre>
java.lang.AssertionError:
Expectation failure on verify:
documentChanged("Document"): expected: 3, actual: 2
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...
</pre>
<h3>
Specifying Return Values
</h3>
<p>
For specifying return values,
we wrap the expected call in <code>expect(T value)</code> and specify the return value
with the method <code>andReturn(Object returnValue)</code> on the object returned by
<code>expect(T value)</code>.
</p>
<p>
As an example, we check the workflow for document
removal. If <code>ClassUnderTest</code> gets a call for document
removal, it asks all collaborators for their vote for removal
with calls to <code>byte voteForRemoval(String title)</code> value.
Positive return values are a vote for
removal. If the sum of all values is positive, the document is removed
and <code>documentRemoved(String title)</code> is called on
all collaborators:
</p>
<pre>
@Test
public void testVoteForRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote for it
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
mock.documentRemoved("Document"); // expect document removal
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertTrue(classUnderTest.removeDocument("Document"));
verify(mock);
}
@Test
public void testVoteAgainstRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote against it
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertFalse(classUnderTest.removeDocument("Document"));
verify(mock);
}
</pre>
<p>
The type of the returned value is checked at compile time. As an example,
the following code will not compile, as the type of the provided return value
does not match the method's return value:
</p>
<pre>
expect(mock.voteForRemoval("Document")).andReturn("wrong type");
</pre>
<p>
Instead of calling <code>expect(T value)</code>
to retrieve the object for setting the return value,
we may also use the object returned by <code>expectLastCall()</code>.
Instead of
</p>
<pre>
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
</pre>
<p>
we may use
</p>
<pre>
mock.voteForRemoval("Document");
expectLastCall().andReturn((byte) 42);
</pre>
<p>
This type of specification should only be used if the line gets too long,
as it does not support type checking at compile time.
</p>
<h3>
Working with Exceptions
</h3>
<p>
For specifying exceptions (more exactly: Throwables) to be thrown, the object returned by
<code>expectLastCall()</code> and <code>expect(T value)</code> provides the method
<code>andThrow(Throwable throwable)</code>.
The method has to be called in record state after the call to the Mock Object for
which it specifies the <code>Throwable</code> to be thrown.
</p>
<p>
Unchecked exceptions (that is, <code>RuntimeException</code>, <code>Error</code>
and all their subclasses) can be thrown from every method. Checked exceptions can only be
thrown from the methods that do actually throw them.
</p>
<h3>
Creating Return Values or Exceptions
</h3>
<p>
Sometimes we would like our mock object to return a value or throw an exception
that is created at the time of the actual call. Since EasyMock 2.2, the object returned by
<code>expectLastCall()</code> and <code>expect(T value)</code> provides the method
<code>andAnswer(IAnswer answer)</code> which allows to specify an implementation of the
interface <code>IAnswer</code> that is used to create the return value or exception.
</p>
<p>
Inside an <code>IAnswer</code> callback, the arguments passed to the mock call
are available via <code>EasyMock.getCurrentArguments()</code>.
If you use these, refactorings like reordering parameters may break your tests.
You have been warned.
</p>
<p>
An alternative to <code>IAnswer</code> are the <code>andDelegateTo</code> and
<code>andStubDelegateTo</code> methods. They allow to delegate the call to a
concrete implementation of the mocked interface that will then provide the answer.
The pros are that the arguments found in <code>EasyMock.getCurrentArguments()</code>
for <code>IAnswer</code> are now passed to the method of the concrete implementation.
This is refactoring safe. The cons are that you have to provide an implementation
which is kind of doing a mock manually... Which is what you try to avoid by
using EasyMock. It can also be painful if the interface has many methods. Finally,
the type of the concrete class can't be checked statically against the mock type.
If for some reason, the concrete class isn't implementing the method that is
delegated, you will get an exception during the replay only. However, this
case should be quite rare.
</p>
<p>
To understand correctly the two options, here is an example:
</p>
<pre>
List<String> l = createMock(List.class);
// andAnswer style
expect(l.remove(10)).andAnswer(new IAnswer<String>() {
public String answer() throws Throwable {
return getCurrentArguments()[0].toString();
}
});
// andDelegateTo style
expect(l.remove(10)).andDelegateTo(new ArrayList<String>() {
@Override
public String remove(int index) {
return Integer.toString(index);
}
});
</pre>
<h3>
Changing Behavior for the Same Method Call
</h3>
<p>
It is also possible to specify a changing behavior for a method.
The methods <code>times</code>, <code>andReturn</code>, and <code>andThrow</code>
may be chained. As an example, we define <code>voteForRemoval("Document")</code> to
</p>
<ul>
<li>return 42 for the first three calls,
</li>
<li>throw a <code>RuntimeException</code> for the next four calls,
</li>
<li>return -42 once.
</li>
</ul>
<pre>
expect(mock.voteForRemoval("Document"))
.andReturn((byte) 42).times(3)
.andThrow(new RuntimeException(), 4)
.andReturn((byte) -42);
</pre>
<h3>
Relaxing Call Counts
</h3>
<p>
To relax the expected call counts, there are additional methods
that may be used instead of <code>times(int count)</code>:
</p>
<dl>
<dt><code>times(int min, int max)</code></dt>
<dd>to expect between <code>min</code> and <code>max</code> calls,</dd>
<dt><code>atLeastOnce()</code></dt>
<dd>to expect at least one call, and</dd>
<dt><code>anyTimes()</code></dt>
<dd>to expected an unrestricted number of calls.</dd>
</dl>
<p>
If no call count is specified, one call is expected. If we would like to state this
explicitely, <code>once()</code> or <code>times(1)</code> may be used.
</p>
<h3>
Strict Mocks
</h3>
<p>
On a Mock Object returned by a <code>EasyMock.createMock()</code>,
the order of method calls is not checked.
If you would like a strict Mock Object that checks the order of method calls,
use <code>EasyMock.create<i>Strict</i>Mock()</code> to create it.</p>
<p>
If an unexpected method is called on a strict Mock Object,
the message of the exception will show the method calls
expected at this point followed by the first conflicting one.
<code>verify(mock)</code> shows all missing method calls.
</p>
<h3>
Switching Order Checking On and Off
</h3>
<p>
Sometimes, it is necessary to have a Mock Object that checks the order of only some calls.
In record phase, you may switch order checking on by calling <code>checkOrder(mock, true)</code>
and switch it off by calling <code>checkOrder(mock, false)</code>.
</p>
<p>
There are two differences between a strict Mock Object and a normal Mock Object:
</p>
<ol>
<li> A strict Mock Object has order checking enabled after creation. </li>
<li> A strict Mock Object has order checking enabled after reset (see <em>Reusing a Mock Object</em>). </li>
</ol>
<h3>
Flexible Expectations with Argument Matchers
</h3>
<p>
To match an actual method call on the Mock Object with an
expectation, <code>Object</code> arguments are by default compared with
<code>equals()</code>. This may lead to problems. As an example,
we consider the following expectation:
</p>
<pre>
String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(documents)).andReturn(42);
</pre>
<p>
If the method is called with another array with the same contents,
we get an exception, as <code>equals()</code> compares object
identity for arrays:
</p>
<pre>
java.lang.AssertionError:
Unexpected method call voteForRemovals([Ljava.lang.String;@9a029e):
voteForRemovals([Ljava.lang.String;@2db19d): expected: 1, actual: 0
documentRemoved("Document 1"): expected: 1, actual: 0
documentRemoved("Document 2"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.voteForRemovals(Unknown Source)
at org.easymock.samples.ClassUnderTest.listenersAllowRemovals(ClassUnderTest.java:88)
at org.easymock.samples.ClassUnderTest.removeDocuments(ClassUnderTest.java:48)
at org.easymock.samples.ExampleTest.testVoteForRemovals(ExampleTest.java:83)
...
</pre>
<p>
To specify that only array equality is needed for this call, we may use the method
<code>aryEq</code> that is statically imported from the <code>EasyMock</code> class:
</p>
<pre>
String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(aryEq(documents))).andReturn(42);
</pre>
<p>
If you would like to use matchers in a call, you have to specify matchers for all
arguments of the method call.
</p>
<p>
There are a couple of predefined argument matchers available.
</p>
<dl>
<dt><code>eq(X value)</code></dt>
<dd>Matches if the actual value is equals the expected value. Available for all primitive types and for objects.</dd>
<dt><code>anyBoolean()</code>, <code>anyByte()</code>, <code>anyChar()</code>, <code>anyDouble()</code>, <code>anyFloat()</code>, <code>anyInt()</code>, <code>anyLong()</code>, <code>anyObject()</code>, <code>anyObject(Class clazz)</code>, <code>anyShort()</code>, <code>anyString()</code></dt>
<dd>Matches any value. Available for all primitive types and for objects.</dd>
<dt><code>eq(X value, X delta)</code></dt>
<dd>Matches if the actual value is equal to the given value allowing the given delta. Available for <code>float</code> and <code>double</code>.</dd>
<dt><code>aryEq(X value)</code></dt>
<dd>Matches if the actual value is equal to the given value according to <code>Arrays.equals()</code>. Available for primitive and object arrays.</dd>
<dt><code>isNull()</code>, <code>isNull(Class clazz)</code></dt>
<dd>Matches if the actual value is null. Available for objects.</dd>
<dt><code>notNull()</code>, <code>notNull(Class clazz)</code></dt>
<dd>Matches if the actual value is not null. Available for objects.</dd>
<dt><code>same(X value)</code></dt>
<dd>Matches if the actual value is the same as the given value. Available for objects.</dd>
<dt><code>isA(Class clazz)</code></dt>
<dd>Matches if the actual value is an instance of the given class, or if it is in instance of a class that extends or implements the given class. Null always return false. Available for objects.</dd>
<dt><code>lt(X value)</code>, <code>leq(X value)</code>, <code>geq(X value)</code>, <code>gt(X value)</code></dt>
<dd>Matches if the actual value is less/less or equal/greater or equal/greater than the given value. Available for all numeric primitive types and <code>Comparable</code>.</dd>
<dt><code>startsWith(String prefix), contains(String substring), endsWith(String suffix)</code></dt>
<dd>Matches if the actual value starts with/contains/ends with the given value. Available for <code>String</code>s.</dd>
<dt><code>matches(String regex), find(String regex)</code></dt>
<dd>Matches if the actual value/a substring of the actual value matches the given regular expression. Available for <code>String</code>s.</dd>
<dt><code>and(X first, X second)</code></dt>
<dd>Matches if the matchers used in <code>first</code> and <code>second</code> both match. Available for all primitive types and for objects.</dd>
<dt><code>or(X first, X second)</code></dt>
<dd>Matches if one of the matchers used in <code>first</code> and <code>second</code> match. Available for all primitive types and for objects.</dd>
<dt><code>not(X value)</code></dt>
<dd>Matches if the matcher used in <code>value</code> does not match.</dd>
<dt><code>cmpEq(X value)</code></dt>
<dd>Matches if the actual value is equals according to <code>Comparable.compareTo(X o)</code>. Available for all numeric primitive types and <code>Comparable</code>.</dd>
<dt><code>cmp(X value, Comparator<X> comparator, LogicalOperator operator)</code></dt>
<dd>Matches if <code>comparator.compare(actual, value) operator 0</code> where the operator is <,<=,>,>= or ==. Available for objects.</dd>
<dt><code>capture(Capture<T> capture)</code>, <code>captureXXX(Capture<T> capture)</code></dt>
<dd>Matches any value but captures it in the <code>Capture</code> parameter for later access. You can do <code>and(someMatcher(...), capture(c))</code> to
capture a parameter from a specific call to the method. You can also specify a <code>CaptureType</code> telling that a given <code>Capture</code> should keep
the first, the last, all or no captured values.</dd>
</dl>
<h3>
Defining your own Argument Matchers
</h3>
<p>
Sometimes it is desirable to define own argument matchers. Let's say that an
argument matcher is needed that matches an exception if the given exception has the same type and an equal message.
It should be used this way:
</p>
<pre>
IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);
</pre>
<p>
Two steps are necessary to achieve this: The new argument matcher has to be defined,
and the static method <code>eqException</code> has to be declared.
</p>
<p>
To define the new argument matcher, we implement the interface <code>org.easymock.IArgumentMatcher</code>.
This interface contains two methods: <code>matches(Object actual)</code> checks whether the actual argument
matches the given argument, and <code>appendTo(StringBuffer buffer)</code> appends a string representation
of the argument matcher to the given string buffer. The implementation is straightforward:
</p>
<pre>
import org.easymock.IArgumentMatcher;
public class ThrowableEquals implements IArgumentMatcher {
private Throwable expected;
public ThrowableEquals(Throwable expected) {
this.expected = expected;
}
public boolean matches(Object actual) {
if (!(actual instanceof Throwable)) {
return false;
}
String actualMessage = ((Throwable) actual).getMessage();
return expected.getClass().equals(actual.getClass())
&& expected.getMessage().equals(actualMessage);
}
public void appendTo(StringBuffer buffer) {
buffer.append("eqException(");
buffer.append(expected.getClass().getName());
buffer.append(" with message \"");
buffer.append(expected.getMessage());
buffer.append("\"")");
}
}
</pre>
<p>
The method <code>eqException</code> must create the argument matcher with the given Throwable,
report it to EasyMock via the static method <code>reportMatcher(IArgumentMatcher matcher)</code>,
and return a value so that it may be used inside the call
(typically <code>0</code>, <code>null</code> or <code>false</code>). A first attempt may look like:
</p>
<pre>
public static Throwable eqException(Throwable in) {
EasyMock.reportMatcher(new ThrowableEquals(in));
return null;
}
</pre>
<p>
However, this only works if the method <code>logThrowable</code> in the example usage accepts
<code>Throwable</code>s, and does not require something more specific like a <code>RuntimeException</code>.
In the latter case, our code sample would not compile:
</p>
<pre>
IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);
</pre>
<p>
Java 5.0 to the rescue: Instead of defining <code>eqException</code> with a <code>Throwable</code> as
parameter and return value, we use a generic type that extends <code>Throwable</code>:
</p>
<pre>
public static <T extends Throwable> T eqException(T in) {
reportMatcher(new ThrowableEquals(in));
return null;
}
</pre>
<h3>
Reusing a Mock Object
</h3>
<p>
Mock Objects may be reset by <code>reset(mock)</code>.
</p>
<p>
If needed, a mock can also be converted from one type to another by calling <code>resetToNice(mock)</code>,
<code>resetToDefault(mock)</code> ou <code>resetToStrict(mock)</code>.
</p>
<h3>
Using Stub Behavior for Methods
</h3>
<p>
Sometimes, we would like our Mock Object to respond to some method calls, but we do not want to
check how often they are called, when they are called, or even if they are called at all.
This stub behavoir may be defined by using the methods <code>andStubReturn(Object value)</code>,
<code>andStubThrow(Throwable throwable)</code>, <code>andStubAnswer(IAnswer<Tgt; answer)</code>
and <code>asStub()</code>. The following code
configures the MockObject to answer 42 to <code>voteForRemoval("Document")</code> once
and -1 for all other arguments:
</p>
<pre>
expect(mock.voteForRemoval("Document")).andReturn(42);
expect(mock.voteForRemoval(not(eq("Document")))).andStubReturn(-1);
</pre>
<h3>
Nice Mocks
</h3>
<p>
On a Mock Object returned by <code>createMock()</code> the default behavior for all methods is to throw an
<code>AssertionError</code> for all unexpected method calls.
If you would like a "nice" Mock Object that by default allows all method calls and returns
appropriate empty values (<code>0</code>, <code>null</code> or <code>false</code>), use <code>create<i>Nice</i>Mock()</code> instead.
</p>
<a id="Object_Methods"/><h3>Object Methods</h3>
<p>
The behavior for the four Object methods <code>equals()</code>,
<code>hashCode()</code>, <code>toString()</code> and <code>finalize()</code>
cannot be changed for Mock Objects created with EasyMock,
even if they are part of the interface for which the
Mock Object is created.
</p>
<h3>Checking Method Call Order Between Mocks</h3>
<p>
Up to this point, we have seen a mock object as a single object that is configured by static methods
on the class <code>EasyMock</code>. But many of these static methods just identify the hidden control of the Mock Object
and delegate to it. A Mock Control is an object implementing the <code>IMocksControl</code> interface.
</p>
<p>
So instead of
</p>
<pre>
IMyInterface mock = createStrictMock(IMyInterface.class);
replay(mock);
verify(mock);
reset(mock);
</pre>
<p>
we may use the equivalent code:
</p>
<pre>
IMocksControl ctrl = createStrictControl();
IMyInterface mock = ctrl.createMock(IMyInterface.class);
ctrl.replay();
ctrl.verify();
ctrl.reset();
</pre>
<p>
The IMocksControl allows to create more than one Mock Object, and so it is possible to check the order of method calls
between mocks. As an example, we set up two mock objects for the interface <code>IMyInterface</code>, and we expect the calls
<code>mock1.a()</code> and <code>mock2.a()</code> ordered, then an open number of calls to <code>mock1.c()</code>
and <code>mock2.c()</code>, and finally <code>mock2.b()</code> and <code>mock1.b()</code>, in this order:
</p>
<pre>
IMocksControl ctrl = createStrictControl();
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);
mock1.a();
mock2.a();
ctrl.checkOrder(false);
mock1.c();
expectLastCall().anyTimes();
mock2.c();
expectLastCall().anyTimes();
ctrl.checkOrder(true);
mock2.b();
mock1.b();
ctrl.replay();
</pre>
<h3>Naming Mock Objects</h3>
<p>
Mock Objects can be named at creation using
<code>createMock(String name, Class<T> toMock)</code>,
<code>createStrictMock(String name, Class<T> toMock)</code> or
<code>createNiceMock(String name, Class<T> toMock)</code>.
The names will be shown in exception failures.
</p>
<h3>Serializing Mocks</h3>
<p>
Mocks can be serialized at any time during their life. However, there are some obvious contraints:
</p>
<ul>
<li>All used matchers should be serializable (all genuine EasyMock ones are)
</li>
<li>Recorded parameters should also be serializable
</li>
</ul>
<h3>Multithreading</h3>
<p>
During recording, a mock is <b>not</b> thread-safe. So a giving mock (or mocks linked to the same <code>IMocksControl</code>)
can only be recorded from a single thread. However, different mocks can be recorded simultaneously in different threads.
</p>
<p>
During the replay phase, mocks are by default thread-safe. This can be change for a given mock if <code>makeThreadSafe(mock, false)</code>
is called during the recording phase. This can prevent deadlocks in some rare situations.
</p>
<p>
Finally, calling <code>checkIsUsedInOneThread(mock, true)</code> on a mock will make sure the mock is used in only one thread and
throw an exception otherwise. This can be handy to make sure a thread-unsafe mocked object is used correctly.
</p>
<h3>EasyMockSupport</h3>
<p>
<code>EasyMockSupport</code> is a class that meant to be used as a helper or base class to your test cases. It will automatically registers all
created mocks (or in fact all created controls) and to replay, reset or verify them in batch instead of explicitly. Here's a JUnit example:
</p>
<pre>
public class SupportTest extends EasyMockSupport {
private Collaborator firstCollaborator;
private Collaborator secondCollaborator;
private ClassTested classUnderTest;
@Before
public void setup() {
classUnderTest = new ClassTested();
}
@Test
public void addDocument() {
// creation phase
firstCollaborator = createMock(Collaborator.class);
secondCollaborator = createMock(Collaborator.class);
classUnderTest.addListener(firstCollaborator);
classUnderTest.addListener(secondCollaborator);
// recording phase
firstCollaborator.documentAdded("New Document");
secondCollaborator.documentAdded("New Document");
replayAll(); // replay all mocks at once
// test
classUnderTest.addDocument("New Document", new byte[0]);
verifyAll(); // verify all mocks at once
}
}
</pre>
<h3>Altering EasyMock default behavior</h3>
<p>
EasyMock provides a property mecanisim allowing to alter its behavior. It mainly aims
at allowing to use a legacy behavior on a new version. Currently supported properties are:
</p>
<dl>
<dt><code>easymock.notThreadSafeByDefault</code></dt>
<dd>If true, a mock won't be thread-safe by default. Possible values are "true" or "false". Default is false</dd>
<dt><code>easymock.enableThreadSafetyCheckByDefault</code></dt>
<dd>If true, thread-safety check feature will be on by default. Possible values are "true" or "false". Default is false</dd>
<dt><code>easymock.disableClassMocking</code></dt>
<dd>Do not allow class mocking (only allow interface mocking). Possible values are "true" or "false". Default is false.</dd>
</dl>
<p>
Properties can be set in two ways.
</p>
<ul>
<li>In an <code>easymock.properties</code> file set in the classpath default package
</li>
<li>By calling <code>EasyMock.setEasyMockProperty</code>. Constants are available
in the <code>EasyMock</code> class. Setting properties in the code obviously override any property set
in <code>easymock.properties</code>
</li>
</ul>
<h3>Backward Compatibility</h3>
<p>EasyMock 3 still has a Class Extension project (although deprecated) to
allow an easier migration from EasyMock 2 to EasyMock 3. It is a source not a binary
compatibility. So the code will need to be recompiled.
</p>
<p>EasyMock 2.1 introduced a callback feature that has been removed in EasyMock 2.2,
as it was too complex. Since EasyMock 2.2, the <code>IAnswer</code> interface
provides the functionality for callbacks.
</p>
<h3>OSGi</h3>
<p>
EasyMock jar can be used as an OSGi bundle. It exports <code>org.easymock</code>,
<code>org.easymock.internal</code> and <code>org.easymock.internal.matchers</code>
packages. However, to import the two latter, you need to specify the <code>poweruser</code>
attribute at true (<code>poweruser=true</code>). These packages are meant to be
used to extend EasyMock so they usually don't need to be imported.
</p>
<h3>Partial mocking</h3>
<p>
Sometimes you may need to mock only some methods of a class and keep
the normal behavior of others. This usually happens when you want to
test a method that calls some others in the same class. So you want to
keep the normal behavior of the tested method and mock the others.
</p>
<p>
In this case, the first thing to do is to consider a refactoring since
most of the time this problem caused by a bad design. If it's not
the case or if you can't do otherwise because of some development constraints,
here's the solution.
</p>
<pre>
ToMock mock = createMockBuilder(ToMock.class)
.addMockedMethod("mockedMethod").createMock();
</pre>
<p>In this case only the methods added with <code>addMockedMethod(s)</code> will be
mocked (<code>mockedMethod()</code> in the example). The others will still
behave as they used to. One exception: abstract methods are conveniently mocked by default.
</p>
<p><code>createMockBuilder</code> returns a <code>IMockBuilder</code> interface. It contains various methods to
easily create a partial mock. Have a look at the javadoc.
</p>
<p>
<b>Remark:</b> EasyMock provides a default behavior for Object's methods (<i>equals, hashCode, toString, finalize</i>).
However, for a partial mock, if these methods are not mocked explicitly, they will have their normal behavior
instead of EasyMock default's one.
</p>
<h3>Self testing</h3>
<p>
It is possible to create a mock by calling one of its constructor. This can be handy when a
class method needs to be tested but the class other methods, mocked. For that you should do
something like
</p>
<pre>
ToMock mock = createMockBuilder(ToMock.class)
.withConstructor(1, 2, 3); // 1, 2, 3 are the constructor parameters
</pre>
<p>
See the <code>ConstructorCalledMockTest</code> for an example.
</p>
<h3>Replace default class instantiator</h3>
<p>
For some reason (usually an unsupported JVM), it is possible that EasyMock isn't able to mock
a class mock in your environment. Under the hood, class instantiation is implemented with a factory
pattern. In case of failure, you can replace the default instantiator with:
</p>
<ul>
<li>The good old <code>DefaultClassInstantiator</code> which works well with Serializable classes
and otherwise tries to guess the best constructor and parameters to use.</li>
<li>You own instantiator which only needs to implement <code>IClassInstantiator</code>.</li>
</ul>
<p>
You set this new instantiator using <code>ClassInstantiatorFactory.setInstantiator()</code>.
You can set back the default one with <code>setDefaultInstantiator()</code>.
</p>
<p>
<b>Important:</b>
The instantiator is kept statically so it will stick between your unit tests. Make sure you
reset it if needed.
</p>
<h3>Serialize a class mock</h3>
<p>
A class mock can also be serialized. However, since it extends a serializable class, this class
might have defined a special behavior using for instance <code>writeObject</code>. These methods
will still be called when serializing the mock and might fail. The workaround is usually to call
a constructor when creating the mock.
</p>
<p>
Also, de-serializing the mock in a different class loader than the serialization might fail. It wasn't tested.
</p>
<h3>Class Mocking Limitations</h3>
<ul>
<li>To be coherent with interface mocking, EasyMock provides a built-in behavior
for <code>equals()</code>, <code>toString()</code>, <code>hashCode()</code> and <code>finalize()</code>
even for class mocking. It means that you cannot record your own behavior for
these methods. This limitation is considered to be a feature
that prevents you from having to care about these methods.
</li>
<li>Final methods cannot be mocked. If called, their normal code will be executed.
</li>
<li>Private methods cannot be mocked. If called, their normal code will be executed.
During partial mocking, if your method under test is calling some private methods,
you will need to test them as well since you cannot mock them.
</li>
<li>Class instantiation is performed using
<a href="http://objenesis.googlecode.com/svn/docs/index.html">Objenesis</a>.
Supported JVMs are listed
<a href="http://code.google.com/p/objenesis/wiki/ListOfCurrentlySupportedVMs">here</a>.
</li>
</ul>
<h3>Android support</h3>
<p>
Since EasyMock 3.2, EasyMock can be used on Android VM (Dalvik). Just add a dependency to you apk project used to test
your application. It is a good idea to exclude cglib since dexmaker is used instead. You should also add dexmaker
explicitly because it's an optional dependency. If you use Maven, the final required dependencies will look like this
</p>
<pre>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.2</version>
<exclusions>
<exclusion>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.dexmaker</groupId>
<artifactId>dexmaker</artifactId>
<version>1.0</version>
</dependency>
</pre>
<h2>
EasyMock Development
</h2>
<p>
EasyMock has been developed by Tammo Freese at OFFIS. It is maintained by Henri Tremblay
since 2007. The development of EasyMock is hosted on <a href="http://sourceforge.net/projects/easymock/">SourceForge</a>
to allow other developers and companies to contribute.
</p>
<p>
Class mocking (previously known as EasyMock Class Extension) was initially developed
by Joel Shellman, Chad Woolley and Henri Tremblay on the files section of Yahoo!Groups.
</p>
<p>
Thanks to the people who gave feedback or provided patches, including
Nascif Abousalh-Neto, Dave Astels, Francois Beausoleil, George Dinwiddie, Shane Duan,
Wolfgang Frech, Steve Freeman, Oren Gross, John D. Heintz, Dale King, Brian Knorr,
Dierk Koenig, Chris Kreussling, Robert Leftwich, Patrick Lightbody, Johannes Link,
Rex Madden, David McIntosh, Karsten Menne, Bill Michell,
Stephan Mikaty, Ivan Moore, Ilja Preuss, Justin Sampson, Markus Schmidlin, Richard Scott,
Joel Shellman, Jiří Mareš, Alexandre de Pellegrin
Shaun Smith, Marco Struck, Ralf Stuckert, Victor Szathmary, Bill Uetrecht,
Frank Westphal, Chad Woolley, Bernd Worsch,
Rodrigo Damazio, Bruno Fonseca, Ben Hutchison and numerous others.
</p>
<p>
Please check the <a href="http://www.easymock.org">EasyMock home page</a> for new versions,
and send bug reports and suggestions to the
<a href="mailto:easymock@yahoogroups.com?subject=EasyMock ${project.version} feedback">EasyMock Yahoo!Group</a>.
If you would like to subscribe to the EasyMock Yahoo!Group, send a message to
<a href="mailto:easymock-subscribe@yahoogroups.com">easymock-subscribe@yahoogroups.com</a>.
</p>
</div>
</body>
</html>
|