0

I want to modify the behavior of some function without being the author of that function. What I control is that I can ask the author to follow some pattern, e.g. use a base class, use a certain decorator, property etc.

If in python, I would use a decorator to change the behavior of a method.

As an example, My goal: Improve code coverage by automatically testing over multiple input data.

Pseudo code:

@implementation SomeTestSuiteClass
// If in python, I would add a decorator here to change the behavior of the following method
-(void)testSample1 {
  input = SpecialProvider();
  output = FeatureToTest(input);
  SpecialAssert(output);
}
@end

What I want: During test, the testSample1 method will be called multiple times. Each time, the SpecialProvider will emit a different input data. Same for the SpecialAssert, which can verify the output corresponding to the given input.

SpecialProvider and SpecialAssert will be API under my control/ownership (i.e. I write them). The SomeTestSuiteClass together with the testSample1 will be written by the user (i.e. test writer).

Is there a way for Objective-C to achieve "what I want" above?

Shichu Zhu
  • 311
  • 3
  • 15

1 Answers1

0

You could mock objects and/or its methods using objective-c runtime or some third party frameworks. I discourage it though. That is a sign of poor architecture choices in the 1st place. The main problem in your approach are hidden dependencies in your code directly referencing SpecialProvider & SpecialAssert symbols directly. A much better way to this would be like this:

-(void)testSample1:(SpecialProvider*)provider assert:(BOOL (^)(parameterTypes))assertBlock {
  input = provider;
  output = FeatureToTest(input);
  if (assertBlock != nil) {
      assertBlock(output);
  }
}

Since Objective-c does not support default argument values like Swift does you could emulate it with:

-(void)testSample1 {
    [self testSample1:DefaultSpecialProvider() assert:DefaultAssert()];
}

not to call the explicit -(void)testSample1:(SpecialProvider*)provider assert:(BOOL (^)(parameterTypes))assertBlock all the time, however in tests you would always use the explicit 2 argument variant to allow substituting the implementation(s) not being under test.

Further improvement idea:
Put the SpecialProvider and SpecialAssert behind protocols(i.e. equivalent of interfaces in other programming languages) so you can easily exchange the implementation.

Kamil.S
  • 5,205
  • 2
  • 22
  • 51
  • Thanks for improving the design! A question I didn't get: In your structure, how can `testSample1` be run multiple times with different input data from Provider? FYI I did some search and [this](https://stackoverflow.com/a/50090681/5426033) seems to be a good direction to achieve this. – Shichu Zhu Jul 10 '19 at 18:58
  • You would use a different subclass of the `SpecialProvider*` for the passed argument to achieve different behavior. Alternatively the arg would be of `SpecialProviding` protocol type and you would use distinct implementations of it (e.g. `SpecialProviderForProduction` vs `SpecialProviderMock`) – Kamil.S Jul 10 '19 at 19:07
  • Does this mean the user (test writers) has to explicitly invoke `testSample` multiple times (e.g. in a for loop), each time to test over one input? – Shichu Zhu Jul 10 '19 at 19:09
  • `testSample` not necessarily would get invoked with distinct args in each loop step (but could if you wish so). Each test should pass the args according to its needs. By doing this through args and not some crazy dependency route you avoid the risk of tests affecting each others outcome. You have strict control over it. – Kamil.S Jul 10 '19 at 19:14