I am using OCMock v3 do unit testing, I want to test the following piece of code:
#implementation School
-(void) handleStudent:(Student*) student{
Bool result = [self checkIdentityWithName:student.name age:student.age];
...
}
...
#end
In my following test case I created a student instance with name "John", age 23. and then I run the function under test:
-(void) testHandleStudent{
Student *student = [Student initWithName:#"John" age:23];
// function under test
[schoolPartialMock handleStudent:student];
// I want to not only verify checkIdentityWithName:age: get called,
// but also check the exact argument is passed in. that's John 23 in this case
// how to check argument ?
}
In my test case, I want to verify that the exact arguments values are passed into function checkIdentityWithName:age: . that's name "John" and age 23 are used. How to verify that in OCMock v3? (There is no clear example in its documentation how to do it.)
You can make it like that
-(void) testHandleStudent{
id studentMock = OCMClassMock([Student class]);
OCMStub([studentMock name]).andReturn(#"John");
OCMStub([studentMock age]).andReturn(23);
[schoolPartialMock handleStudent:studentMock];
OCMVerify([schoolPartialMock checkIdentityWithName:#"John" age:23]);
}
or
-(void) testHandleStudent{
id studentMock = OCMClassMock([Student class]);
OCMStub([studentMock name]).andReturn(#"John");
OCMStub([studentMock age]).andReturn(23);
OCMExpect([schoolPartialMock checkIdentityWithName:#"John" age:23]);
[schoolPartialMock handleStudent:studentMock];
OCMVerifyAll(schoolPartialMock);
}
Hope this help
Related
I'm writing a UI Test for a white label project where each app has a different set of menu items. The test taps on each menu item and takes a screenshot (using fastlane snapshot).
Currently this all happens inside one XCTestCase called testScreenshotAllMenuItems() which looks like this:
func testScreenshotAllMenuItems() {
// Take a screenshot of the menu
openTheMenu()
snapshot("Menu")
var cells:[XCUIElement] = []
// Store each menu item for use later
for i in 0..<app.tables.cells.count {
cells.append(app.tables.cells.element(boundBy: i))
}
// Loop through each menu item
for menuItem in cells.enumerated() {
let exists = menuItem.element.waitForExistence(timeout: 5)
if exists && menuItem.element.isHittable {
// Only tap on the menu item if it isn't an external link
let externalLink = menuItem.element.children(matching: .image)["external link"]
if !externalLink.exists {
var name = "\(menuItem.offset)"
let cellText = menuItem.element.children(matching: .staticText).firstMatch
if cellText.label != "" {
name += "-\(cellText.label.replacingOccurrences(of: " ", with: "-"))"
}
print("opening \(name)")
menuItem.element.tap()
// Screenshot this view and then re-open the menu
snapshot(name)
openTheMenu()
}
}
}
}
I'd like to be able to dynamically generate each screenshot as it's own test case so that these will be reported correctly as individual tests, maybe something like:
[T] Screenshots
[t] testFavouritesViewScreenShot() ✓
[t] testGiveFeedbackViewScreenShot() ✓
[t] testSettingsViewScreenShot() ✓
I've had a look at the documentation on creating tests programmatically but I'm not sure how to set this up in a swifty fashion. - Ideally I would use closures to wrap the existing screenshot tests in to their own XCTestCase - I imagined this like the following but there doesn't appear to be any helpful init methods to make this happen:
for menuItem in cells {
let test = XCTestCase(closure: {
menuItem.tap()
snapshot("menuItemName")
})
test.run()
}
I don't understand the combination of invocations and selectors that the documentation suggests using and I can't find any good examples, please point me in the right direction and or share any examples you have of this working.
You probably can't do it in pure swift since NSInvocation is not part of swift api anymore.
XCTest rely on + (NSArray<NSInvocation *> *)testInvocations function to get list of test methods inside one XCTestCase class. Default implementation as you can assume just find all methods that starts with test prefix and return them wrapped in NSInvocation. (You could read more about NSInvocation here)
So if we want to have tests declared in runtime, this is point of interest for us.
Unfortunately NSInvocation is not part of swift api anymore and we cannot override this method.
If you OK to use little bit of ObjC then we can create super class that hide NSInvocation details inside and provide swift-friendly api for subclasses.
/// Parent.h
/// SEL is just pointer on C struct so we cannot put it inside of NSArray.
/// Instead we use this class as wrapper.
#interface _QuickSelectorWrapper : NSObject
- (instancetype)initWithSelector:(SEL)selector;
#end
#interface ParametrizedTestCase : XCTestCase
/// List of test methods to call. By default return nothing
+ (NSArray<_QuickSelectorWrapper *> *)_qck_testMethodSelectors;
#end
/// Parent.m
#include "Parent.h"
#interface _QuickSelectorWrapper ()
#property(nonatomic, assign) SEL selector;
#end
#implementation _QuickSelectorWrapper
- (instancetype)initWithSelector:(SEL)selector {
self = [super init];
_selector = selector;
return self;
}
#end
#implementation ParametrizedTestCase
+ (NSArray<NSInvocation *> *)testInvocations {
// here we take list of test selectors from subclass
NSArray<_QuickSelectorWrapper *> *wrappers = [self _qck_testMethodSelectors];
NSMutableArray<NSInvocation *> *invocations = [NSMutableArray arrayWithCapacity:wrappers.count];
// And wrap them in NSInvocation as XCTest api require
for (_QuickSelectorWrapper *wrapper in wrappers) {
SEL selector = wrapper.selector;
NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.selector = selector;
[invocations addObject:invocation];
}
/// If you want to mix parametrized test with normal `test_something` then you need to call super and append his invocations as well.
/// Otherwise `test`-prefixed methods will be ignored
return invocations;
}
+ (NSArray<_QuickSelectorWrapper *> *)_qck_testMethodSelectors {
return #[];
}
#end
So now our swift test classes need to just inherit from this class and override _qck_testMethodSelectors:
/// RuntimeTests.swift
class RuntimeTests: ParametrizedTestCase {
/// This is our parametrized method. For this example it just print out parameter value
func p(_ s: String) {
print("Magic: \(s)")
}
override class func _qck_testMethodSelectors() -> [_QuickSelectorWrapper] {
/// For this example we create 3 runtime tests "test_a", "test_b" and "test_c" with corresponding parameter
return ["a", "b", "c"].map { parameter in
/// first we wrap our test method in block that takes TestCase instance
let block: #convention(block) (RuntimeTests) -> Void = { $0.p(parameter) }
/// with help of ObjC runtime we add new test method to class
let implementation = imp_implementationWithBlock(block)
let selectorName = "test_\(parameter)"
let selector = NSSelectorFromString(selectorName)
class_addMethod(self, selector, implementation, "v#:")
/// and return wrapped selector on new created method
return _QuickSelectorWrapper(selector: selector)
}
}
}
Expected output:
Test Suite 'RuntimeTests' started at 2019-03-17 06:09:24.150
Test Case '-[ProtocolUnitTests.RuntimeTests test_a]' started.
Magic: a
Test Case '-[ProtocolUnitTests.RuntimeTests test_a]' passed (0.006 seconds).
Test Case '-[ProtocolUnitTests.RuntimeTests test_b]' started.
Magic: b
Test Case '-[ProtocolUnitTests.RuntimeTests test_b]' passed (0.001 seconds).
Test Case '-[ProtocolUnitTests.RuntimeTests test_c]' started.
Magic: c
Test Case '-[ProtocolUnitTests.RuntimeTests test_c]' passed (0.001 seconds).
Test Suite 'RuntimeTests' passed at 2019-03-17 06:09:24.159.
Kudos to Quick team for super class implementation.
Edit: I created repo with example github
My code invokes a C library function:
#implementation Store
...
-(void) doWork {
// this is a C function from a library
int data = getData();
...
}
end
I am unit testing the above function, I want to mock the C function getData() in my test, here is my test case:
#interface StoreTests : XCTestCase {
int mData;
Store *store;
}
#end
#implementation StoreTests
-(void) setUp {
[super setUp];
mData = 0;
store = [[Store alloc] init];
}
-(void) testDoWork {
// this call will use the mocked getData(), no problem here.
[store doWork];
}
// mocked getData()
int getData() {
mData = 10; // Use of undeclared identifier 'mData', why?
return mData;
}
...
#end
Why I get complier error:
Use of undeclared identifier 'mData' inside mocked getData() function?
You are misunderstanding how instance methods and variables work.
Every instance method has a variable self which references the current instance (or "current object") and a use of an instance variable, such as mData, is shorthand for accessing that variable using self, e.g self->mData, where -> is the (Objective-)C operator for field access. So your setup method written "long hand" is:
-(void) setUp {
[super setUp];
self->mData = 0;
self->store = [[Store alloc] init];
}
But where does self, the reference to the instance, itself come from? Well it's not magical, just hidden, it is passed to an instance method automatically as a hidden extra argument. At this point which switch to pseudo-code to show this. Your setup method is effectively compiled as:
-(void) setUp withSelf:(StoreTest *)self {
[super setUp];
self->mData = 0;
self->store = [[Store alloc] init];
}
and a call such as:
StoreTests *myStoreTests = ...
[myStoreTests setup];
is effectively compiled as something like:
[myStoreTests setup withSelf:myStoreTests];
automatically adding the extra self argument.
Now all the above only applies to methods, and enables them to access instance variables and methods, it does not apply to plain C functions - they have no hidden self argument and cannot access instance variables.
The solution you mention in the answer you added of declaring mData outside of the interface:
int mData;
#interface StoreTests : XCTestCase {
Store *store;
}
#end
changes mData into a global variable, instead of being an instance variable. C functions can access global variables. However this does mean that every instance of the class shares the same mData, there is only one mData in this case rather than one for every instance.
Making an instance variable into a global is therefore not a general solution to to issues like this, however as it is unlikely that you will have more than one instance of your StoreTests class it is a suitable solution in this case.
You should however make one change: you can only have one global variable with a given name with a program, so your mData must be unique and is accessible by any code within your program, not just the code of StoreTests. You can mitigate this my declaring the variable as static:
static int mData;
this keeps the variable as global but only makes it visible to code within the same file as the declaration, which is probably just the code of StoreTests.
HTH
I found one solution for my question, that is declare mData above #interface StoreTests : XCTestCase, something like this:
int mData;
#interface StoreTests : XCTestCase {
Store *store;
}
#end
...
This question already has an answer here:
How to mock an object with OCMock which isn't passed as a parameter to method?
(1 answer)
Closed 6 years ago.
I am using OCMock 3 to write my unite tests in iOS project.
I have a foo method under School class:
#implementation School
-(NSString *)foo:
{
// I need to mock this MyService instance in my test
MyService *service = [[MyService alloc] init];
// I need to stub the function return for [service getStudent]
Student *student = [service getStudent];
if (student.age == 12) {
//log the age is 12
} else {
//log the age is not 12
}
...
}
The Student looks like this:
#interface Student : NSObject
#property NSInteger age;
...
#end
In my test case, I want to stub the method call [service getStudent] to return a Student instance with age value 12 I defined:
// use mocked service
id mockService = OCMClassMock([MyService class]);
OCMStub([[mockService alloc] init]).andReturn(mockService);
// create a student instance (with age=12) which I want to return by function '-(Student*) getStudent:'
Student *myStudent = [[Student alloc] init];
myStudent.age = 12;
// stub function to return 'myStudent'
OCMStub([mockService getStudent]).andReturn(myStudent);
// execute foo method
id schoolToTest = [OCMockObject partialMockForObject:[[School alloc] init]];
[schoolToTest foo];
When I run my test case, however, the student returned by -(Student*)getStudent: method is not with age 12, why?
===== UPDATE ====
I noticed in internet, somebody suggested to separate the alloc and init to stub. I also tried it but it doesn't work as it says:
// use mocked service
id mockService = OCMClassMock([MyService class]);
OCMStub([mockService alloc]).andReturn(mockService);
OCMStub([mockService init]).andReturn(mockService);
// stub function to return 'myStudent'
OCMStub([mockService getStudent]).andReturn(myStudent);
When I do this & run my test case, the real implementation of -(Student*)getStudent: method get called... I can't understand why people says it works.
You cannot mock the init method. This is stated in the documentation (Section 9.3), but maybe it's too hidden.
all:
I've the following unit tests class (implementation file shown):
#implementation SampleTests {
A* c;
NSString* token;
}
- (void)setUp
{
[super setUp];
// Set-up code here.
c = [[A alloc] init];
}
- (void)tearDown
{
// Tear-down code here.
[super tearDown];
}
- (void)testA
{
token = #"sample";
}
-(void)testB
{
[c method:token]; // <-- fails because token is nil, but c has a correct value. Why!
}
#end
When I run my tests, testB fails because token is nil, but c it's ok, so why is it token destroyed?
Every time you run your unit tests, each test case is invoked independently. Before each test case runs, the setUp method is called, and afterwards the tearDown method is called.
So if you want to share token among tests, you should add
token = #"sample"; // or smth else
to your setUp method.
As far as I know, the current implementation runs the test methods in their alphabetic order, so your example should run without problems.
Usually, if I want something to be tested first, I name the methods test1_criticalFeature, test2_dependentFeatures etc.
The order that test methods are executed in is not guaranteed, so it could be the case that testB is run before testA or even in the future that they are run in parallel.
I using Xcode 4.6 for calculator application and I am getting three errors, can you shed a light what might causing them?
Here are the 3 errors
property 'pushElement' not find on object type "CalculatorBrain",
property 'enter pressed' not found on object type CalculatorviewController
property 'perform operation' not find on object type "CalculatorBrain"
This is part of the code that i am getting error in it which CalculatorviewController.m
- (IBAction)enterPressed:(UIButton*)sender {
[_Brain.pushElement :self.display.text]; ......first error ....
userIntheMiddleOfEnteringText= NO;
}
- (IBAction)operationPressed:(UIButton *)sender {
if (userIntheMiddleOfEnteringText)
self.enterPressed; ........second error.....
else {
double result = [_Brain.performOperation]; ... third error...
self.display.text=[NSString stringWithFormat:#"%g",result];
}
}
and the CalculatorBrain.m code is
#import "CalculatorBrain.h"
#implementation CalculatorBrain
-(void)pushElement:(double)operand {
NSNumber *operandObject=[NSNumber numberWithDouble:operand];
[self.stack addObject:operandObject];
}
-(double) popElement {
NSNumber *popedNumber=[self.stack lastObject];
if (popedNumber)
{
[_stack removeLastObject];
}
return [popedNumber doubleValue];
}
-(double)performOperation:(NSString*)operation {
double result = 0;
if( [operation isEqualToString: #"+"]){
result =self. popElement + self.popElement;
}
else if ([operation isEqualToString: #"*"]){
result=self.popElement*self.popElement;
}
else if ([operation isEqualToString:#"-" ]) {
double operand=self.popElement;
result=self.popElement - operand ;
}
else if ([operation isEqualToString:#"/"]) {
double divisor =self.popElement;
result= self.popElement/ divisor;
}
return result;
}
#end
All three errors are simple errors that indicate that you don't yet understand the basics of Objective-C. You need to learn the language first. Learn how to call methods and pass parameters.
The first error:
[_Brain.pushElement :self.display.text];
This should be:
[_Brain pushElement:self.display.text];
Here you want to call the pushElement: method on the _Brain object.
The second error:
self.enterPressed;
That indicates that you are trying to accessing a property named enterPressed on self. But there is no such property. There is a method named enterPressed: and this takes a UIButton as an argument. So you need to call it like this:
[self enterPress:sender];
The third error:
double result = [_Brain.performOperation];
Here you want to call the performOperation method but there isn't one. There is a method named performOperation:. This takes a parameter of type NSString. It must be called like this:
double result = [_Brain performOperation:#"some operation"];
The missing piece here is the operation you wish to pass. There is no obvious indication of where that comes from. Perhaps the button title.