Is there a clean way to change a mock's method behavior based on other method's invocation?
Example of code under test, service will be mocked by Mockito in the test:
public Bar foo(String id) {
Bar b = service.retrieveById(id);
boolean flag = service.deleteById(id);
b = service.retrieveById(id); //this should throw an Exception
return b;
}
Here, we would like service.retrieveById to return an object, unless service.delete has been called.
Chaining behaviours could work in this simple case, but it doesn'd consider the invocation of the other method deleteById (imagine refactoring).
when(service.retrieveById(any())).
.thenReturn(new Bar())
.thenThrow(new RuntimeException())
I am wondering for example if it's possible to implement an Answer object which can detect whether deleteById has been invoked. Or if there is a totally different approach which would make the test cleaner.
In my eyes, this is a good example of over-engeneering mock objects.
Don't try to make your mocks behave like "the real thing".
That is not what mocking should be used for when writing tests.
The test is not about Service itself, it's about some class that makes use of it.
If Service either returns something for a given Id, or raises an exception when there is no result, make 2 individual test cases!
we can't foresee the reason of the refactoring.. maybe there will be n call to retrieve before the delete.. So this is really about tying the two methods behavior together.
Yes, and someone could add another twelve methods that all influence the outcome of deleteById. Will you be keeping track?
Use stubbing only to make it run.
Consider writing a fake if Service is rather simple and doesn't change much. Remember mocking is just one tool. Sometimes there are alternatives.
Considering what I've just said, this might send you mixed messages but since StackOverflow was down for a while and I'm currently working heavily with Mockito myself, I spent some time with your other question:
I am wondering for example if it's possible to implement an Answer object which can detect whether deleteById has been invoked.
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.function.Supplier;
import static java.util.Objects.requireNonNull;
/**
* An Answer that resolves differently depending on a specified condition.
*
* <p>This implementation is NOT thread safe!</p>
*
* #param <T> The result type
*/
public class ConditionalAnswer <T> implements Answer<T> {
/**
* Create a new ConditionalAnswer from the specified result suppliers.
*
* <p>On instantiation, condition is false</p>
*
* #param whenConditionIsFalse The result to supply when the underlying
condition is false
* #param whenConditionIsTrue The result to supply when the underlying
condition is true
* #param <T> The type of the result to supply
* #return A new ConditionalAnswer
*/
public static <T> ConditionalAnswer<T> create (
final Supplier<T> whenConditionIsFalse,
final Supplier<T> whenConditionIsTrue) {
return new ConditionalAnswer<>(
requireNonNull(whenConditionIsFalse, "whenConditionIsFalse"),
requireNonNull(whenConditionIsTrue, "whenConditionIsTrue")
);
}
/**
* Create a Supplier that on execution throws the specified Throwable.
*
* <p>If the Throwable turns out to be an unchecked exception it will be
* thrown directly, if not it will be wrapped in a RuntimeException</p>
*
* #param throwable The throwable
* #param <T> The type that the Supplier officially provides
* #return A throwing Supplier
*/
public static <T> Supplier<T> doThrow (final Throwable throwable) {
requireNonNull(throwable, "throwable");
return () -> {
if (RuntimeException.class.isAssignableFrom(throwable.getClass())) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
};
}
boolean conditionMet;
final Supplier<T> whenConditionIsFalse;
final Supplier<T> whenConditionIsTrue;
// Use static factory method instead!
ConditionalAnswer (
final Supplier<T> whenConditionIsFalse,
final Supplier<T> whenConditionIsTrue) {
this.whenConditionIsFalse = whenConditionIsFalse;
this.whenConditionIsTrue = whenConditionIsTrue;
}
/**
* Set condition to true.
*
* #throws IllegalStateException If condition has been toggled already
*/
public void toggle () throws IllegalStateException {
if (conditionMet) {
throw new IllegalStateException("Condition can only be toggled once!");
}
conditionMet = true;
}
/**
* Wrap the specified answer so that before it executes, this
* ConditionalAnswer is toggled.
*
* #param answer The ans
* #return The wrapped Answer
*/
public Answer<?> toggle (final Answer<?> answer) {
return invocation -> {
toggle();
return answer.answer(invocation);
};
}
#Override
public T answer (final InvocationOnMock invocation) throws Throwable {
return conditionMet ? whenConditionIsTrue.get() : whenConditionIsFalse.get();
}
/**
* Test whether the underlying condition is met
* #return The state of the underlying condition
*/
public boolean isConditionMet () {
return conditionMet;
}
}
I wrote some tests to make it work. This is how it would look applied to the Service example:
#Test
void conditionalTest (
#Mock final Service serviceMock, #Mock final Bar barMock) {
final var id = "someId"
// Create shared, stateful answer
// First argument: Untill condition changes, return barMock
// Second: After condition has changed, throw Exception
final var conditional = ConditionalAnswer.create(
() -> barMock,
ConditionalAnswer.doThrow(new NoSuchElementException(someId)));
// Whenever retrieveById is invoked, the call will be delegated to
// conditional answer
when(service.retrieveById(any())).thenAnswer(conditional);
// Now we can define, what makes the condition change.
// In this example it is service#delete but it could be any other
// method on any other class
// Option 1: Easy but ugly
when(service.deleteById(any())).thenAnswer(invocation -> {
conditional.toggle();
return Boolean.TRUE;
});
// Option 2: Answer proxy
when(service.deleteById(any()))
.thenAnswer(conditional.toggle(invocation -> Boolean.TRUE));
// Now you can retrieve by id as many times as you like
assertSame(barMock, serviceMock.retrieveById(someId));
assertSame(barMock, serviceMock.retrieveById(someId));
assertSame(barMock, serviceMock.retrieveById(someId));
assertSame(barMock, serviceMock.retrieveById(someId));
assertSame(barMock, serviceMock.retrieveById(someId));
// Until
assertTrue(serviceMock.deleteById(someId));
// NoSuchElementException
serviceMock.retrieveById(someId)
}
}
The test above might contain errors (I used some classes from the project that I am currently working on).
Thanks for the challenge.
You can use Mockito.verify() to check whether deleteById was called or not:
Mockito.verify(service).deleteById(any());
You can also use Mockito.InOrder for orderly verification (I have not tested the below code):
InOrder inOrder = Mockito.inOrder(service);
inOrder.verify(service).retrieveById(any());
inOrder.verify(service).deleteById(any());
inOrder.verify(service).retrieveById(any());
Related
Is there any way to setup JMock's onConsecutiveCalls method to loop back to the first Action passed once it reaches the end of the list of parameters passed?
In the sample code below, I would like the mock to return true->false->true->Ad infinitum.
Mock setup:
final MyService myServiceMocked = mockery.mock(MyService.class);
mockery.checking(new Expectations() {{
atLeast(1).of(myServiceMocked).doSomething(with(any(String.class)), with(any(String.class)));
will (onConsecutiveCalls(
returnValue(true),
returnValue(false)
));
}});
Method calling doSomething method:
...
for (String id:idList){
boolean first = getMyService().doSomething(methodParam1, someString);
boolean second = getMyService().doSomething(anotherString, id);
}
...
I got around this issue by adding the following to my test class, then calling onRecurringConsecutiveCalls() in place of onConsecutiveCalls().
Additional code:
/**
* Recurring version of {#link org.jmock.Expectations#onConsecutiveCalls(Action...)}
* When last action is executed, loops back to first.
* #param actions Actions to execute.
* #return An action sequence that will loop through the given actions.
*/
public Action onRecurringConsecutiveCalls(Action...actions) {
return new RecurringActionSequence(actions);
}
/**
* Recurring version of {#link org.jmock.lib.action.ActionSequence ActionSequence}
* When last action is executed, loops back to first.
* #author AnthonyW
*/
public class RecurringActionSequence extends ActionSequence {
List<Action> actions;
Iterator<Action> iterator;
/**
* Recurring version of {#link org.jmock.lib.action.ActionSequence#ActionSequence(Action...) ActionSequence}
* When last action is executed, loops back to first.
* #param actions Actions to execute.
*/
public RecurringActionSequence(Action... actions) {
this.actions = new ArrayList<Action>(Arrays.asList(actions));
resetIterator();
}
#Override
public Object invoke(Invocation invocation) throws Throwable {
if (iterator.hasNext())
return iterator.next().invoke(invocation);
else
return resetIterator().next().invoke(invocation);
}
/**
* Resets iterator to starting position.
* #return <code>this.iterator</code> for chain calls.
*/
private Iterator<Action> resetIterator() {
this.iterator = this.actions.iterator();
return this.iterator;
}
}
Note: This code is based on the source code from JMock 2.1.
IMEI I am new to MXbeans. I want to understand when is the MXBean actually initialized, in the application which I am supporting we have a notification system and I can not see my MXBean in JConsole until there is some notification arriving. Here is a code of my MXBean.
package mecomaany.instrumentation;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import BulkCMIRP_MxBean;
import SoapIRPLogger;
/**
* This dynamic MBean exposes a method to return the 10 latest Basic CM
* operations.
*
* #author eshtrom
*
*/
public class BasicCMIRP_MxBean implements DynamicMBean{
private static final String CLASS_NAME = BasicCMIRP_MxBean.class.getCanonicalName();
private static final String MX_BEAN_NAME = BasicCMIRP_MxBean.class.getCanonicalName() + ":Type=BasicCMIRP_MxBean";
private static final String BEAN_DESCRIPTION = "Instrumentation bean for SOAP Basic CM IRP.";
private static final int NUM_OPERATIONS_TO_RECORD = 10;
private final ArrayList<String> basicCMOperations = new ArrayList<String>();
/**
* Register the bean. This is a best effort attempt. If registration fails
* we'll report it but nothing more.
*/
public BasicCMIRP_MxBean() {
registerMxBean();
}
/**
* Attempt to unregister and clean up the MX beans.
*/
public void destroy() {
basicCMOperations.clear();
unregisterMxBean();
}
/**
* This method returns a description of this bean to the JMX interface. The
* description is just a list of names of the currently stored attributes.
*/
public synchronized MBeanInfo getMBeanInfo() {
final MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[0];
final MBeanOperationInfo[] operations = { new MBeanOperationInfo("getBasicCMInstrumentation", "Get instrumentation Basic CM IRP bean.", null, "String[]",
MBeanOperationInfo.INFO) };
return new MBeanInfo(this.getClass().getName(), BEAN_DESCRIPTION, attributes, null, operations, null);
}
/**
* Callback to execute methods exposed by this dynamic MBean.
*/
public Object invoke(final String actionName, final Object[] params, final String[] signature) throws MBeanException, ReflectionException {
SoapIRPLogger.enter(CLASS_NAME, "invoke");
if (actionName != null) {
if (actionName.equals("getBulkCMInstrumentation")) {
return getBasicCMInstrumentation();
}
}
SoapIRPLogger.exit(CLASS_NAME, "invoke");
throw new ReflectionException(new NoSuchMethodException(actionName));
}
/**
* Construct a human readable very of the last 10 operations and return it.
*
* #return string array as an object.
*/
private Object getBasicCMInstrumentation() {
SoapIRPLogger.enter(CLASS_NAME, "getBasicCMInstrumentation");
String[] result = new String[basicCMOperations.size()];
int index = 0;
for (String operation : basicCMOperations) {
result[index] = operation;
index++;
}
SoapIRPLogger.exit(CLASS_NAME, "getBasicCMInstrumentation");
return result;
}
/**
* No attributes are writable so this method will throw an exception.
*/
public void setAttribute(final Attribute arg0) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
throw new AttributeNotFoundException("All attributes on this bean are read only.");
}
/**
* No attributes are writable so this method will return an empty list.
*/
public AttributeList setAttributes(final AttributeList arg0) {
return new AttributeList();
}
public Object getAttribute(final String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
throw new AttributeNotFoundException("No attributes are defined on this MX bean.");
}
/**
* No attributes are readable so this method will return an empty list.
*/
public AttributeList getAttributes(final String[] attributes) {
return new AttributeList();
}
/**
* Add or update and instrumentation attribute. A null attribute name will
* be ignored.
*
* #param name
* #param value
*/
public void updateInstrumentationAttibute(final String name, final String value) {
// The only attribute that this bean supports is Bulk CM operations.
if (name.compareTo("BasicCM_Operations") == 0) {
basicCMOperations.add(value);
// We'll only record a maximum of 10 operations. If we exceed the
// max, remove the oldest.
if (basicCMOperations.size() > NUM_OPERATIONS_TO_RECORD) {
basicCMOperations.remove(0);
}
}
}
public ObjectName getBeanName() throws MalformedObjectNameException, NullPointerException {
// Construct the ObjectName for the MBean we will register
return new ObjectName(MX_BEAN_NAME);
}
/**
* Register the bean.
*/
protected void registerMxBean() {
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(this, this.getBeanName());
} catch (Exception e) {
SoapIRPLogger.exception(CLASS_NAME, "Constructor", "Failed to register BulkCMIRP_MxBean management beans.", e);
}
}
protected void unregisterMxBean() {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.getBeanName());
} catch (Exception e) {
// Suppress exceptions, we're closing down anyway.
}
}
}
Loner;
Your MXBean should be visible in JConsole as soon as you construct it, since the constructor calls registerMxBean() which registers the MBean with the platform MBeanServer.
Assuming your JConsole is already running when the MBean is registered, JConsole's view of the MBean will be dictated by the first instance of the MBeanInfo that is generated on registration. Since you coded this as a DynamicMBean, I wonder if your intent was to periodically modify the MBeanInfo, but be aware that once a JConsole connection has retrieved the MBeanInfo for a registered bean, it will not refresh it. If the MBeanInfo changes, you must closed the connection in JConsole and open it again.
Consequently, your issue may be outside the code you have supplied, or more simply put, when is the instance of BasicCMIRP_MxBean created ?
Your comment about not seeing the bean until notifications start arriving makes me wonder if you mean that:
A. You cannot see the MBean until notifications start arriving, in which case I would assume that there is some activation code that creates the instance when a [the first ?] notification arrives, or
B. What you mean is that you cannot see specific properties of the MBean until notifications start arriving.
Perhaps you could clarify the larger picture here.
Some additional observations that may be unrelated:
The MBeanInfo you are generating specifies that the one and only operation is called getBasicCMInstrumentation (basic), but your invoke handler only attempts to decode an operation called getBulkCMInstrumentation (bulk). JConsole is supplied the MBeanInfo in order to render the operations so the console believes that the operation is called basic but the MBean will only respond to a request called bulk.
The signature for method getBasicCMInstrumentation returns an Object but what the code really returns is a String[]. This may work because your MBeanInfo specifies that a String[] is returned, but for consistency and clarity I would change the method signature.
//Nicholas
Upon building an MVC framework in PHP I ran into a problem which could be solved easily using Java style generics. An abstract Controller class might look something like this:
abstract class Controller {
abstract public function addModel(Model $model);
There may be a case where a subclass of class Controller should only accept a subclass of Model. For example ExtendedController should only accept ReOrderableModel into the addModel method because it provides a reOrder() method that ExtendedController needs to have access to:
class ExtendedController extends Controller {
public function addModel(ReOrderableModel $model) {
In PHP the inherited method signature has to be exactly the same so the type hint cannot be changed to a different class, even if the class inherits the class type hinted in the superclass. In java I would simply do this:
abstract class Controller<T> {
abstract public addModel(T model);
class ExtendedController extends Controller<ReOrderableModel> {
public addModel(ReOrderableModel model) {
But there is no generics support in PHP. Is there any solution which would still adhere to OOP principles?
Edit
I am aware that PHP does not require type hinting at all but it is perhaps bad OOP. Firstly it is not obvious from the interface (the method signature) what kind of objects should be accepted. So if another developer wanted to use the method it should be obvious that objects of type X are required without them having to look through the implementation (method body) which is bad encapsulation and breaks the information hiding principle. Secondly because there's no type safety the method can accept any invalid variable which means manual type checking and exception throwing is needed all over the place!
It appears to work for me (though it does throw a Strict warning) with the following test case:
class PassMeIn
{
}
class PassMeInSubClass extends PassMeIn
{
}
class ClassProcessor
{
public function processClass (PassMeIn $class)
{
var_dump (get_class ($class));
}
}
class ClassProcessorSubClass extends ClassProcessor
{
public function processClass (PassMeInSubClass $class)
{
parent::processClass ($class);
}
}
$a = new PassMeIn;
$b = new PassMeInSubClass;
$c = new ClassProcessor;
$d = new ClassProcessorSubClass;
$c -> processClass ($a);
$c -> processClass ($b);
$d -> processClass ($b);
If the strict warning is something you really don't want, you can work around it like this.
class ClassProcessor
{
public function processClass (PassMeIn $class)
{
var_dump (get_class ($class));
}
}
class ClassProcessorSubClass extends ClassProcessor
{
public function processClass (PassMeIn $class)
{
if ($class instanceof PassMeInSubClass)
{
parent::processClass ($class);
}
else
{
throw new InvalidArgumentException;
}
}
}
$a = new PassMeIn;
$b = new PassMeInSubClass;
$c = new ClassProcessor;
$d = new ClassProcessorSubClass;
$c -> processClass ($a);
$c -> processClass ($b);
$d -> processClass ($b);
$d -> processClass ($a);
One thing you should bear in mind though, this is strictly not best practice in OOP terms. If a superclass can accept objects of a particular class as a method argument then all its subclasses should also be able of accepting objects of that class as well. Preventing subclasses from processing classes that the superclass can accept means you can't use the subclass in place of the superclass and be 100% confident that it will work in all cases. The relevant practice is known as the Liskov Substitution Principle and it states that, amongst other things, the type of method arguments can only get weaker in subclasses and the type of return values can only get stronger (input can only get more general, output can only get more specific).
It's a very frustrating issue, and I've brushed up against it plenty of times myself, so if ignoring it in a particular case is the best thing to do then I'd suggest that you ignore it. But don't make a habit of it or your code will start to develop all kinds of subtle interdependencies that will be a nightmare to debug (unit testing won't catch them because the individual units will behave as expected, it's the interaction between them where the issue lies). If you do ignore it, then comment the code to let others know about it and that it's a deliberate design choice.
Whatever the Java world invented need not be always right. I think I detected a violation of the Liskov substitution principle here, and PHP is right in complaining about it in E_STRICT mode:
Cite Wikipedia: "If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program."
T is your Controller. S is your ExtendedController. You should be able to use the ExtendedController in every place where the Controller works without breaking anything. Changing the typehint on the addModel() method breaks things, because in every place that passed an object of type Model, the typehint will now prevent passing the same object if it isn't accidentally a ReOrderableModel.
How to escape this?
Your ExtendedController can leave the typehint as is and check afterwards whether he got an instance of ReOrderableModel or not. This circumvents the PHP complaints, but it still breaks things in terms of the Liskov substitution.
A better way is to create a new method addReOrderableModel() designed to inject ReOrderableModel objects into the ExtendedController. This method can have the typehint you need, and can internally just call addModel() to put the model in place where it is expected.
If you require an ExtendedController to be used instead of a Controller as parameter, you know that your method for adding ReOrderableModel is present and can be used. You explicitly declare that the Controller will not fit in this case. Every method that expects a Controller to be passed will not expect addReOrderableModel() to exist and never attempt to call it. Every method that expects ExtendedController has the right to call this method, because it must be there.
class ExtendedController extends Controller
{
public function addReOrderableModel(ReOrderableModel $model)
{
return $this->addModel($model);
}
}
My workaround is the following:
/**
* Generic list logic and an abstract type validator method.
*/
abstract class AbstractList {
protected $elements;
public function __construct() {
$this->elements = array();
}
public function add($element) {
$this->validateType($element);
$this->elements[] = $element;
}
public function get($index) {
if ($index >= sizeof($this->elements)) {
throw new OutOfBoundsException();
}
return $this->elements[$index];
}
public function size() {
return sizeof($this->elements);
}
public function remove($element) {
validateType($element);
for ($i = 0; $i < sizeof($this->elements); $i++) {
if ($this->elements[$i] == $element) {
unset($this->elements[$i]);
}
}
}
protected abstract function validateType($element);
}
/**
* Extends the abstract list with the type-specific validation
*/
class MyTypeList extends AbstractList {
protected function validateType($element) {
if (!($element instanceof MyType)) {
throw new InvalidArgumentException("Parameter must be MyType instance");
}
}
}
/**
* Just an example class as a subject to validation.
*/
class MyType {
// blahblahblah
}
function proofOfConcept(AbstractList $lst) {
$lst->add(new MyType());
$lst->add("wrong type"); // Should throw IAE
}
proofOfConcept(new MyTypeList());
Though this still differs from Java generics, it pretty much minimalizes the extra code needed for mimicking the behaviour.
Also, it is a bit more code than some examples given by others, but - at least to me - it seems to be more clean (and more simliar to the Java counterpart) than most of them.
I hope some of you will find it useful.
Any improvements over this design are welcome!
I did went through the same type of problem before. And I used something like this to tackle it.
Class Myclass {
$objectParent = "MyMainParent"; //Define the interface or abstract class or the main parent class here
public function method($classObject) {
if(!$classObject instanceof $this -> objectParent) { //check
throw new Exception("Invalid Class Identified");
}
// Carry on with the function
}
}
You can consider to switch to Hack and HHVM. It is developed by Facebook and full compatible to PHP. You can decide to use <?php or <?hh
It support that what you want:
http://docs.hhvm.com/manual/en/hack.generics.php
I know this is not PHP. But it is compatible with it, and also improves your performance dramatically.
You can do it dirtily by passing the type as a second argument of the constructor
<?php class Collection implements IteratorAggregate{
private $type;
private $container;
public function __construct(array $collection, $type='Object'){
$this->type = $type;
foreach($collection as $value){
if(!($value instanceof $this->type)){
throw new RuntimeException('bad type for your collection');
}
}
$this->container = new \ArrayObject($collection);
}
public function getIterator(){
return $this->container->getIterator();
}
}
To provide a high level of static code-analysis, strict typing and usability, i came up with this solution: https://gist.github.com/rickhub/aa6cb712990041480b11d5624a60b53b
/**
* Class GenericCollection
*/
class GenericCollection implements \IteratorAggregate, \ArrayAccess{
/**
* #var string
*/
private $type;
/**
* #var array
*/
private $items = [];
/**
* GenericCollection constructor.
*
* #param string $type
*/
public function __construct(string $type){
$this->type = $type;
}
/**
* #param $item
*
* #return bool
*/
protected function checkType($item): bool{
$type = $this->getType();
return $item instanceof $type;
}
/**
* #return string
*/
public function getType(): string{
return $this->type;
}
/**
* #param string $type
*
* #return bool
*/
public function isType(string $type): bool{
return $this->type === $type;
}
#region IteratorAggregate
/**
* #return \Traversable|$type
*/
public function getIterator(): \Traversable{
return new \ArrayIterator($this->items);
}
#endregion
#region ArrayAccess
/**
* #param mixed $offset
*
* #return bool
*/
public function offsetExists($offset){
return isset($this->items[$offset]);
}
/**
* #param mixed $offset
*
* #return mixed|null
*/
public function offsetGet($offset){
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
/**
* #param mixed $offset
* #param mixed $item
*/
public function offsetSet($offset, $item){
if(!$this->checkType($item)){
throw new \InvalidArgumentException('invalid type');
}
$offset !== null ? $this->items[$offset] = $item : $this->items[] = $item;
}
/**
* #param mixed $offset
*/
public function offsetUnset($offset){
unset($this->items[$offset]);
}
#endregion
}
/**
* Class Item
*/
class Item{
/**
* #var int
*/
public $id = null;
/**
* #var string
*/
public $data = null;
/**
* Item constructor.
*
* #param int $id
* #param string $data
*/
public function __construct(int $id, string $data){
$this->id = $id;
$this->data = $data;
}
}
/**
* Class ItemCollection
*/
class ItemCollection extends GenericCollection{
/**
* ItemCollection constructor.
*/
public function __construct(){
parent::__construct(Item::class);
}
/**
* #return \Traversable|Item[]
*/
public function getIterator(): \Traversable{
return parent::getIterator();
}
}
/**
* Class ExampleService
*/
class ExampleService{
/**
* #var ItemCollection
*/
private $items = null;
/**
* SomeService constructor.
*
* #param ItemCollection $items
*/
public function __construct(ItemCollection $items){
$this->items = $items;
}
/**
* #return void
*/
public function list(){
foreach($this->items as $item){
echo $item->data;
}
}
}
/**
* Usage
*/
$collection = new ItemCollection;
$collection[] = new Item(1, 'foo');
$collection[] = new Item(2, 'bar');
$collection[] = new Item(3, 'foobar');
$collection[] = 42; // InvalidArgumentException: invalid type
$service = new ExampleService($collection);
$service->list();
Even if something like this would feel so much better:
class ExampleService{
public function __construct(Collection<Item> $items){
// ..
}
}
Hope generics will get into PHP soon.
One alternative is the combination of splat operator + typed hint + private array:
<?php
class Student {
public string $name;
public function __construct(string $name){
$this->name = $name;
}
}
class Classe {
private $students = [];
public function add(Student ...$student){
array_merge($this->students, $student);
}
public function getAll(){
return $this->students;
}
}
$c = new Classe();
$c->add(new Student('John'), new Student('Mary'), new Student('Kate'));
Need help is deciding what approach needs to be taken to test below piece of code
I have one method called
private messageDAOInf messageDAO;
public Response verifyUser(Request request) {
Response response = null;
if (someCondition) {
/* -----------Some processing here---------- */
} else {
response = constructResponse(errorCode, errorDesc);
}
// Do more processing with messages from response
response = messageDAOInf
.convertMessagesAsAppropriate(response);
return response;
}
My EasyMock code is here
/** The message dao inf. */
private MessageDAOInf messageDAOInf;
private VerifyUserService verifyUserServiceI;
#Before
public void setUp() throws Exception {
messageDAOInf = EasyMock.createMock(MessageDAOInf.class);
verifyUserService = new VerifyUserService();
verifyUserService.setMessageDAOInf(messageDAOInf);
}
#Test
public void testErrorResponse() {
Request request = loadRequest();
Response response = constructErrorResponse();
EasyMock.expect(messageDAOInf.convertMessagesAsAppropriate(
response)).andReturn(response);
EasyMock.replay(messageDAOInf);
Response response2 = verifyUserService.verifyUser(request);
assertFailedResponse(response2);
}
The issue is from line
response = constructResponse(errorCode, errorDesc);
it constructs error response in verifyUser method and passes it to
messageDAOInf.convertMessagesAsAppropriate()
But with easy mock it passes some other instance (mocked one) and hence failes with error
java.lang.AssertionError:
Unexpected method call convertMessagesAsAppropriate(***Response#1bb35b***):
convertMessagesAsAppropriate(***Response#1b5d2b2***): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:56)
Let me know what approach I should take.
Thank you.
Your initial code expects that convertMessagesAsAppropriate will be called with the exact instance of Response that you created in the test: obviously it will not do that.
The correction you've made is essentially the same as using the built-in EasyMock.anyObject() method which will allow any Response instance. If that's all you want to check in your unit test, that's fine. Alternatively you can add extra logic into your ArgumentMatcher to prove that the Response that is passed as an argument really is an ErrorResponse, or Capture the response and examine it in your test. This all depends on your level of testing :-)
I have found out way of doing it.
You need to implement interface org.easymock.IArgumentMatcher
public class ObjectEquals implements IArgumentMatcher {
/** The expected. */
private Object expected;
/**
* Instantiates a new criterion equals.
*
* #param expected
* the expected
*/
public ObjectEquals(final Object expected) {
this.expected = expected;
}
/* (non-Javadoc)
* #see org.easymock.IArgumentMatcher#matches(java.lang.Object)
*/
public boolean matches(final Object actual) {
return expected.getClass().equals(actual.getClass());
}
/* (non-Javadoc)
* #see org.easymock.IArgumentMatcher#appendTo(java.lang.StringBuffer)
*/
public void appendTo(final StringBuffer buffer) {
buffer.append("buffer(");
}
}
and in your test class add method
/*
* Eq criterion.
*
* #param criterion the criterion
*
* #return the criterion
*/
public static <T> T eqCriterion(final Class<T> className, Object object) {
EasyMock.reportMatcher(new ObjectEquals(object));
return null;
}
Now while passing to easymock use method eqCriterion at line
EasyMock.expect(messageDAOInf.convertMessagesAsAppropriate(
response)).andReturn(response);
In short replace above line with
EasyMock.expect(messageDAOInf.convertMessagesAsAppropriate(
eqCriterion(Response.class, response))).andReturn(response);
This way it will use this mocked response instance instead of one generated by actual code.
public Object doSomething(Object o); which I want to mock. It should just return its parameter. I tried:
Capture<Object> copyCaptcher = new Capture<Object>();
expect(mock.doSomething(capture(copyCaptcher)))
.andReturn(copyCatcher.getValue());
but without success, I get just an AssertionError as java.lang.AssertionError: Nothing captured yet. Any ideas?
Well, the easiest way would be to just use the Capture in the IAnswer implementation... when doing this inline you have to declare it final of course.
MyService mock = createMock(MyService.class);
final Capture<ParamAndReturnType> myCapture = new Capture<ParamAndReturnType>();
expect(mock.someMethod(capture(myCapture))).andAnswer(
new IAnswer<ParamAndReturnType>() {
#Override
public ParamAndReturnType answer() throws Throwable {
return myCapture.getValue();
}
}
);
replay(mock)
This is probably the most exact way, without relying on some context information. This does the trick for me every time.
I was looking for the same behavior, and finally wrote the following :
import org.easymock.EasyMock;
import org.easymock.IAnswer;
/**
* Enable a Captured argument to be answered to an Expectation.
* For example, the Factory interface defines the following
* <pre>
* CharSequence encode(final CharSequence data);
* </pre>
* For test purpose, we don't need to implement this method, thus it should be mocked.
* <pre>
* final Factory factory = mocks.createMock("factory", Factory.class);
* final ArgumentAnswer<CharSequence> parrot = new ArgumentAnswer<CharSequence>();
* EasyMock.expect(factory.encode(EasyMock.capture(new Capture<CharSequence>()))).andAnswer(parrot).anyTimes();
* </pre>
* Created on 22 juin 2010.
* #author Remi Fouilloux
*
*/
public class ArgumentAnswer<T> implements IAnswer<T> {
private final int argumentOffset;
public ArgumentAnswer() {
this(0);
}
public ArgumentAnswer(int offset) {
this.argumentOffset = offset;
}
/**
* {#inheritDoc}
*/
#SuppressWarnings("unchecked")
public T answer() throws Throwable {
final Object[] args = EasyMock.getCurrentArguments();
if (args.length < (argumentOffset + 1)) {
throw new IllegalArgumentException("There is no argument at offset " + argumentOffset);
}
return (T) args[argumentOffset];
}
}
I wrote a quick "how to" in the javadoc of the class.
Hope this helps.
Captures are for testing the values passed to the mock afterwards. If you only need a mock to return a parameter (or some value calculated from the parameter), you just need to implement IAnswer.
See "Remi Fouilloux"s implementation if you want a reusable way of passing paramter X back, but ignore his use of Capture in the example.
If you just want to inline it like "does_the_trick"s example, again, the Capture is a red herring here. Here is the simplified version:
MyService mock = createMock(MyService.class);
expect(mock.someMethod(anyObject(), anyObject()).andAnswer(
new IAnswer<ReturnType>() {
#Override
public ReturnType answer() throws Throwable {
// you could do work here to return something different if you needed.
return (ReturnType) EasyMock.getCurrentArguments()[0];
}
}
);
replay(mock)
Based on #does_the_trick and using lambdas, you can now write the following:
MyService mock = EasyMock.createMock(MyService.class);
final Capture<ParamAndReturnType> myCapture = EasyMock.newCapture();
expect(mock.someMethod(capture(myCapture))).andAnswer(() -> myCapture.getValue());
or without capture as #thetoolman suggested
expect(mock.someMethod(capture(myCapture)))
.andAnswer(() -> (ParamAndReturnType)EasyMock.getCurrentArguments()[0]);
Um, if I understand your question correctly I think you may be over complicating it.
Object someObject = .... ;
expect(mock.doSomething(someObject)).andReturn(someObject);
Should work just fine. Remember you are supplying both the expected parameter and returne value. So using the same object in both works.