Commit 4aaf4523 authored by rohitrao's avatar rohitrao Committed by Commit bot

[ios] Adds ScopedBlockSwizzler.

ScopedBlockSwizzler is a helper class that swizzles methods for use in unittests
only.  It is most commonly used to inject test objects to replace shared
singletons.

BUG=662903

Review-Url: https://codereview.chromium.org/2551083002
Cr-Commit-Position: refs/heads/master@{#436303}
parent ded0021b
...@@ -50,6 +50,7 @@ test("ios_chrome_unittests") { ...@@ -50,6 +50,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/web_resource:unit_tests", "//ios/chrome/browser/web_resource:unit_tests",
"//ios/chrome/common:unit_tests", "//ios/chrome/common:unit_tests",
"//ios/chrome/test:unit_tests", "//ios/chrome/test:unit_tests",
"//ios/chrome/test/base:unit_tests",
] ]
assert_no_deps = ios_assert_no_deps assert_no_deps = ios_assert_no_deps
......
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("base") {
testonly = true
sources = [
"scoped_block_swizzler.h",
"scoped_block_swizzler.mm",
]
deps = [
"//base",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"scoped_block_swizzler_unittest.mm",
]
deps = [
":base",
"//base",
"//testing/gtest",
]
}
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#ifndef IOS_CHROME_TEST_BASE_SCOPED_BLOCK_SWIZZLER_H_
#define IOS_CHROME_TEST_BASE_SCOPED_BLOCK_SWIZZLER_H_
#include <objc/runtime.h>
#include "base/macros.h"
// Helper class that replaces a method implementation with a given block.
// ScopedBlockSwizzler automatically swizzles when it is constructed and
// reinstalls the original method implementation when it goes out of scope.
class ScopedBlockSwizzler {
public:
// Constructs a new ScopedBlockSwizzler object and replaces the implementation
// of |selector| on the |target| class with the given |block|.
// ScopedBlockSwizzler first tries to swizzle a class method; if one is not
// found, it tries to swizzle an instance method. It is an error to pass a
// |selector| that does not exist on the |target| class.
ScopedBlockSwizzler(Class target, SEL selector, id block);
// Destroys the ScopedBlockSwizzler object, removing the swizzled method and
// reinstalling the original method implementation.
virtual ~ScopedBlockSwizzler();
private:
// The method that is to be swizzled. Can be either a class method or an
// instance method.
Method method_;
// The original implementation of the swizzled method, saved so that it can be
// reinstalled when this object goes out of scope.
IMP original_imp_;
DISALLOW_COPY_AND_ASSIGN(ScopedBlockSwizzler);
};
#endif // IOS_CHROME_TEST_BASE_SCOPED_BLOCK_SWIZZLER_H_
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/test/base/scoped_block_swizzler.h"
#include "base/logging.h"
ScopedBlockSwizzler::ScopedBlockSwizzler(Class target, SEL selector, id block) {
method_ = class_getInstanceMethod(target, selector);
if (!method_) {
// Try swizzling a class method instead.
method_ = class_getClassMethod(target, selector);
}
DCHECK(method_);
IMP block_imp = imp_implementationWithBlock(block);
original_imp_ = method_setImplementation(method_, block_imp);
}
ScopedBlockSwizzler::~ScopedBlockSwizzler() {
IMP block_imp = method_setImplementation(method_, original_imp_);
DCHECK(imp_removeBlock(block_imp));
}
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/mac/foundation_util.h"
#import "base/mac/scoped_nsobject.h"
#import "ios/chrome/test/base/scoped_block_swizzler.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
// Class containing two methods that will be swizzled by the unittests.
@interface ScopedBlockSwizzlerTestClass : NSObject
// An NSString property that will be accessed by one of the swizzled methods.
@property(nonatomic, copy) NSString* value;
+ (NSString*)classMethodToSwizzle;
- (NSString*)instanceMethodToSwizzle;
@end
namespace {
NSString* const kOriginalClassValue = @"Bar";
NSString* const kSwizzledClassValue = @"Foo";
NSString* const kOriginalInstanceValue = @"Bizz";
NSString* const kSwizzledInstanceValue = @"Buzz";
// Tests that swizzling a class method works properly.
TEST(ScopedBlockSwizzlerTest, SwizzlingClassMethods) {
EXPECT_NSEQ(kOriginalClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
{
id block = ^NSString*(id self) { return kSwizzledClassValue; };
ScopedBlockSwizzler swizzler([ScopedBlockSwizzlerTestClass class],
@selector(classMethodToSwizzle), block);
EXPECT_NSEQ(kSwizzledClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
}
EXPECT_NSEQ(kOriginalClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
}
// Tests that swizzling an instance method works properly.
TEST(ScopedBlockSwizzlerTest, SwizzlingInstanceMethod) {
base::scoped_nsobject<ScopedBlockSwizzlerTestClass> target(
[[ScopedBlockSwizzlerTestClass alloc] init]);
target.get().value = kSwizzledInstanceValue;
EXPECT_NSEQ(kOriginalInstanceValue, [target instanceMethodToSwizzle]);
EXPECT_FALSE([[target instanceMethodToSwizzle]
isEqualToString:kSwizzledInstanceValue]);
{
id block = ^NSString*(id self) {
return base::mac::ObjCCastStrict<ScopedBlockSwizzlerTestClass>(self)
.value;
};
ScopedBlockSwizzler swizzler([ScopedBlockSwizzlerTestClass class],
@selector(instanceMethodToSwizzle), block);
EXPECT_NSEQ(kSwizzledInstanceValue, [target instanceMethodToSwizzle]);
}
EXPECT_NSEQ(kOriginalInstanceValue, [target instanceMethodToSwizzle]);
}
// Tests that calling |ScopedBlockSwizzler::reset()| properly unswizzles the
// method.
TEST(ScopedBlockSwizzlerTest, TestReset) {
EXPECT_NSEQ(kOriginalClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
id block = ^NSString*(id self) { return kSwizzledClassValue; };
std::unique_ptr<ScopedBlockSwizzler> swizzler(
new ScopedBlockSwizzler([ScopedBlockSwizzlerTestClass class],
@selector(classMethodToSwizzle), block));
EXPECT_NSEQ(kSwizzledClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
swizzler.reset();
EXPECT_NSEQ(kOriginalClassValue,
[ScopedBlockSwizzlerTestClass classMethodToSwizzle]);
}
} // namespace
#pragma mark - ScopedBlockSwizzlerTestClass
@implementation ScopedBlockSwizzlerTestClass
@synthesize value = _value;
+ (NSString*)classMethodToSwizzle {
return kOriginalClassValue;
}
- (NSString*)instanceMethodToSwizzle {
return kOriginalInstanceValue;
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment