Here is the testing code:
id dataControllerMock = [OCMockObject mockForClass:[RAMImsakyaDataController class]];
[[[dataControllerMock expect] andReturn:dataControllerMock] alloc];
(void)[[[dataControllerMock expect] andReturn:dataControllerMock] init];
[[[dataControllerMock stub] andReturn:#"30.06 , 50.67"] getLocationTitle];
[self.viewController viewDidLoad];
XCTAssertTrue([self.viewController.title isEqualToString:#"30.06 , 50.67"], #"View controller title is wrong");
[dataControllerMock verify];
The problem is that the dataControllerMock causing a failure with "unexpected method invoked: getLocationTitle"! I do stub the method. And even if I change the stub to expect, same thing. When I breakpoint inside viewDidLoad, the mock is already there as expected yet it doesn't recognize the getLocationTitle method.
Update: here is the viewDidLoad code
NSString *location = [self.dataController getLocationTitle];
if (location == nil) {
self.title = #"إمساكية رمضان ١٤٣٥ هـ";
} else {
self.title = [NSString stringWithFormat:#"إمساكية رمضان ١٤٣٥ هـ (توقيت %#)", location];
}
Why not take a different approach and use a partial mock?
RAMImsakyaDataController* realObject = [RAMImsakyaDataController new];
id partialObject = [OCMockObject partialMockForObject:realObject];
[[[partialObject stub] andReturn:#"30.06 , 50.67"] getLocationTitle];
[partialObject viewDidLoad]; // Method under test
XCTAssertTrue([partialObject.title isEqualToString:#"30.06 , 50.67"], #"View controller title is wrong");
I find that trying to mock alloc can lead to difficult behavior.
EDIT
// In MyViewController
- (RAMImsakyaDataController*)dataController {
if (!_dataController) {
_dataController = [[RAMImsakyaDataController alloc] init];
}
return _dataController;
}
Then partial mock the VC and replace this method with one that returns your partially mocked data controller.
My guess is that OCMock is failing to correctly mock the data controller's +alloc method so your view controller is using a real data controller instead of a mock.
I've had a difficult time mocking object creation. What I've ended up doing instead of trying to mock +alloc is putting testability stubs on the objects I want to test that create their dependencies, and then I can use a partial mock of the object under test to override the object creation. Like:
#implementation ViewController
- (void)init {
...
_dataController = [self newDataController];
...
}
- (DataController *)newDataController {
return [[DataController alloc] init];
}
#end
And then in my test
ViewController *underTest = [ViewController alloc];
id mockUnderTest = [OCMockObject partialMockForObject:underTest];
id mockDataController = [OCMockObject niceMockForClass:[DataController class]];
[[[mockUnderTest stub] andReturn:[mockDataController retain]] newDataController];
underTest = [underTest init];
Related
Trying to create simple test for following function:
-(void)presentWithString:(NSString *)name
{
CustomVC *customVC = [[CustomVC alloc] initWithName:name];
UINavigationController *nav = [[UINavigationController alloc] init];
nav.viewControllers = #[customVC];
dispatch_async(dispatch_get_main_queue(), ^{
[self.vc presentViewController:nav animated:YES completion:nil];
});
}
I can split this into chunks with dependency injection, but don't know how to write proper test either way. What would be the best practice for this example?
What do you want to test? There are 3 things happening in your method :
CustomVC is created with name passed.
CustomVC is embedded inside navigation controller.
The navigation controller is presented on self.vc.
You can write a test that checks the whole flow :
- (void)testPresentWithString_shouldPresentCustomVC_withPassedName {
// Arrange
NSString *expectedName = #”name”;
XCTestExpectation *exp = [self expectationWothDescription:#”presentVC called”];
TestClass *sut = [[TestClass alloc] init];
id vcMock = OCMClassMock([UIViewController class]);
sut.vc = vcMock;
OCMExpect([vcMock presentViewController:OCM_ANY animated:YES completion:nil]).andDo(^(NSInvocation *invocation) {
UINavigationController *nav = nil;
[invocation getArgument:&nav atIndex:2];
CustomVC *custom = nav.viewControllers.firstObject;
// Assert
XCTAssertNotNil(nav);
XCTAssertTrue([nav isKindOfClass:[UINavigationController class]]);
XCTAssertEqual(nav.viewControllers.count, 1);
XCTAssertNotNil(custom);
XCTAssertTrue([custom isKindOfClass:[CustomVC class]]);
XCTAssertEqual(custom.name, expectedName);
[exp fulfill];
});
// Act
[sut presentWithString:expectedName];
// Assert
[self waitForExpectationsWithTimeout:1 handler:nil];
OCMVerifyAll(vcMock);
// Cleanup
[vcMock stopMocking];
}
This code checks everything that happens in your method - that a method got called with specific arguments, that the first of these arguments was a navigation controller with only CustomVC embedded and that this CustomVC had name set. Obviously I’ve made assumptions that vc property on tested class can be set from outside and that the name on CustomVC can be read. If not, it may be trickier to test some parts of this.
Personally I wouldn’t unit test this. I would test the initialization of CustomVC separately, and put the whole presentation under a UI test.
Let me know if everything is clear!
—
Side note : I wrote this on mobile from memory, so there might be small mistakes in the code. I will update it when I have a chance to check it with Xcode.
I'm new to OCMock, so I may have overlooked something simple, but my issue is that I can't seem to stub the method class on a mock I've created. Here's how I'm setting up part of my test:
// Unit Test
id mock = [OCMockObject mockForClass:[MySubClass class]];
[[[mock stub] andReturn:[MySubClass class]] class];
...
[someObject someMethodWithParam:mock];
...
Here's my implementation of someMethodWithParam::
// Implementation
- (void)someMethodWithParam:(MySuperClass *)param {
[[param class] someClassMethod];
}
The problem is that [param class] returns OCClassMockObject instead of MySubClass. This results in an "unrecognized selector sent to class" error when someClassMethod is called. I've tried using a partial mock, but that didn't seem to help.
Edit:
Here's a simplified test that won't pass:
// Unit Test
id mock = [OCMockObject mockForClass:[MySubClass class]];
[[[mock stub] andReturn:[MySubClass class]] class];
XCTAssertEqual([mock class], [MySubClass class], #"The mock's class should be MySubClass");
Look at your -someMethodWithParam:. It not return anything. It's void. So this is wrong test for this method. You made mock which have to return something even if method can't return anything.
This test should looks like following:
MyClass object = [MyClass new]; /// instance of class which has someMethodWithParam: method
id mock = [OCMockObject partialMockForObject:object];
[[mock expect] someClassMethod];
[mock someMethodWithParam:[OCMArg any]];
[mock verify];
In this method only what you can is test expectation. You can test if method in your method has been called. So you can test if when you call -someMethodWithParam method -someClassMethod has been called. That's all. And you have to create partial mock.
I'm trying to deal with OCMock. I created simple class MyClass.
#interface MyClass : NSObject
- (NSString *)simpleMethod;
#end
#implementation MyClass
- (NSString *)simpleMethod {
[self method];
return #"simple";
}
- (void)method {
NSLog(#"ABC");
}
#end
What I want to check is if method method was invoked when simpleMethod has been called. Now I've got following code but it doesn't work:
- (void)testMethodInvoked
{
id mock = [OCMockObject mockForClass:[MyClass class]];
[[mock stub] simpleMethod];
SEL selector = NSSelectorFromString(#"method");
[[mock expect] methodForSelector:selector];
[mock verify];
}
How should I test this case? I think that is pretty easy to do, but I have no idea how solve this problem.
How to create mock and call method simpleMethod which invoke method method?
Current log:
<unknown>:0: error: -[OCMockTestTests testOne] : OCMockObject[MyClass]: expected method was not invoked: methodForSelector:#selector(method)
You never actually create an object of the class that you want to test. Also, you have to expect first, then invoke the method:
- (void)testMethodInvoked
{
// first create an object that you want to test:
MyClass *object = [[MyClass alloc] init];
// create a partial mock for that object
id mock = [OCMockObject partialMockForObject:object];
// tell the mock object what you expect
[[mock expect] method];
// call the actual method on the mock object
[mock simpleMethod];
// and finally verify
[mock verify];
}
I sometimes find it useful to test "private" methods / implementations -- perhaps don't call it a unit test if that breaks some kind of orthodoxy -- but for a complex implementation I may want to verify behavior on a more granular level than through the external interface.
In any event, I will expose class extension methods by creating a category in the test class:
#interface MyClass (ExposeForTest)
- (void)method;
#end
- (void)testMyClass
{
id mock = [OCMockObject mockForClass:MyClass.class];
[[mock expect] method];
[mock simpleMethod];
}
I've been working on my unit tests for iOS programming, and I've run into a little problem when trying to validate my main class by mocking it's child classes using OCMock and then seeing if the main class adds the child controllers (mockObjects[uiviewContoller]) and then verifying that an object calls a method on each of the child controllers.
The problem is I keep getting a "test failed 'adding a root view controller OCMockObject[UiViewController] as a child of view controller'"
every other time i run the test.
- (void)setUp
{
[super setUp];
testMain = [[UIViewController alloc] init];
}
- (void)tearDown
{
for (UIViewController *testCon in testMain.childViewControllers) {
[testCon removeFromParentViewController];
}
testMain = nil;
[super tearDown];
}
test:
- (void) testDayNightTriggerTriggersAllSubviews{
id mockTopController = [OCMockObject niceMockForClass:[UIViewController class]];
id mockBottomController = [OCMockObject niceMockForClass:[UIViewController class]];
id mockMainScreen = [OCMockObject niceMockForClass:[UIViewController class]];
[[mockTopController expect] dayNightTrigger];
[[mockBottomController expect] dayNightTrigger];
[[mockMainScreen expect] dayNightTrigger];
//trigger
[testMain dayNightTrigger:mockTopController bottom:mockBottomController main:mockMainScreen];
[mockBottomController verify];
[mockTopController verify];
[mockMainScreen verify];
}
Method to verify:
//overload
- (void) dayNightTrigger:(UIViewController *) top bottom:(UIViewController *)bottom main:(UIViewController *)main{
self.bottomMenu = bottom;
self.topMenu = top;
self.mainScreen = main;
[self dayNightTrigger];
}
- (void) dayNightTrigger{
[self.app dayNightTrigger];
[self.bottomMenu dayNightTrigger];
[self.topMenu dayNightTrigger];
[self.mainScreen dayNightTrigger];
}
I was wondering if there is anything wrong with my setup/teardown? or I'm doing something wrong with the OCMock framework, but really as to why I keep getting this error.
I've run into the same issue. I'm guessing that your properties bottomMenu, topMenu, and mainScreen set bottom, top, and main as child view controllers of the other view controller.
Unfortunately, addChildViewController: looks at some value in the UIViewController* structure. Since it's a direct memory access and not a method call, the OCMockObject can't intercept it. As a result, the mock object is (sometimes) treated as being a root view.
The way I found around it was to override addChildViewController: on the object I was testing in the test file and have it do nothing:
#implementation MyViewController (overwriteForTesting)
- (void)addChildViewController:(UIViewController *)childController {
}
#end
This means that it won't add the view controller to its list of children though.
I am very new to IOS programing.
I've created a class, that uses a NSURLConnection to download data async.
I use a delegate that's sent across and in turn updates my father class. Part of updating the father class includes calling a UIView that has been saved in a local property.
Here are some code examples of what I mean:
myClass:
#synthesize myView = _myView;
-(void) loadMetaData
{
if(self.isMetadataLoaded)
{
[self.myView metadataLoaded];
}
else {
[_htmlLinter summarizeUrl:self.originalLink];
}
}
-(void) urlSummarized:(NSDictionary*)data
{
self.productTitle = [data objectForKey:#"title"];
self.productDescription = [data objectForKey:#"description"];
self.provider = [data objectForKey:#"provider_name"];
self.isMetadataLoaded= true;
[self.myView metadataLoaded];
}
htmlLinter:
-(void)summarizeUrl:(NSString*)url
{
NSURL* u = [[NSURL alloc] initWithString:request];
...
...
...
//removed a lot of logic that doesn't seem to be relevant
//Important to notice, that this is being called on a different thread though:
[self embedlyDidLoad:result];
}
-(void) embedlyDidLoad:(id)result
{
NSDictionary* properties = (NSDictionary*)result;
[_facilitator urlSummarized:properties];
}
The strange thing is this:
myClass doesn't remember what self.myView is when accessed through another thread.
This line the problematic one : [self.myView metadataLoaded];
it returns a valid pointer when called initially in loadMetaData,
but when I call it on another thread in urlSummarized, it is nil.
What can be causing that?