Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond XCTest

Beyond XCTest

Slides from my London iOS Developer Group Presentation from May 2014.

Abizer Nasir

May 07, 2014
Tweet

More Decks by Abizer Nasir

Other Decks in Programming

Transcript

  1. BUT… • It can be long winded to write •

    Different initial states usually require different setups • Tends to be focused on class and method level tests • It’s missing some functionality (sort of)
  2. YOU COULD CONSIDER… • Specta/Expecta or Kiwi to write tests

    in a more BDD, RSpec form. • OCMock for simple mocking. • OHHTTPStubs or Nocilla for stubbing network requests.
  3. RUBY HAS RSPEC context "Working with files" do ! before(:each)

    do tests_dir = Dir.pwd + "/tmp/spec/" ! if File.exists? tests_dir FileUtils.rm_rf tests_dir end ! FileUtils.mkdir_p tests_dir ! FileUtils.cp_r Dir.pwd + "/spec/fixtures/input/.", tests_dir end ! after(:each) do tests_dir = Dir.pwd + "/tmp/spec/" ! if File.exists? tests_dir FileUtils.rm_rf tests_dir end end ! describe "#process_file" do ! it "manages to update multiple files" do files = Dir.glob Dir.pwd + "/tmp/spec/*.{h,m}" files.each do |f| Fixbraces.process_file f end ! result = `diff -r --brief #{Dir.pwd + "/tmp/spec/"} #{Dir.pwd + "/spec/fixtures/expected/"}` expect(result).to eq "" end
  4. SPECTA PROVIDES THIS SpecBegin(SomeDescription) __block SomeObject *_someObject; ! beforeEach(^{ //

    Global setup }); ! afterEach(^{ // Global Teardown }); ! describe(@"Some property of the SUT", ^{ __block NSDictionary *_availabilityDictionary; // Local variable to scope context(@"Specific case", ^{ beforeEach(^{ _someObject.property = value; }); afterEach(^{ // Any other clean-up }); it(@"Description of the expectation", ^{ BOOL success = [_someObject someMethod]; // expectation }); it(@"Another action with an expectation", ^{ // }); }); ! SpecEnd
  5. SIMILARLY, WE CAN WRITE describe(@"With a basic availability dictionary with

    no tiers", ^{ __block NSDictionary *_availabilityDictionary; ! context(@"given a valid dictionary", ^{ beforeEach(^{ _availabilityDictionary = @{@"time" : @"09:00", @"duration" : @"2H"}; }); ! afterEach(^{ _availabilityDictionary = nil; }); ! it(@"creates a basic availability object", ^{ TXHAvailability *availability = [TXHAvailability updateForDateCreateIfNeeded:@"2013-12-29" withDictionary:_availabilityDictionary productId:_product.objectID inManagedObjectContext:_moc]; expect(availability).toNot.beNil(); expect(availability.dateString).to.equal(@"2013-12-29"); expect(availability.timeString).to.equal(@"09:00"); expect(availability.duration).to.equal(@"2H"); }); });
  6. TEST ASYNCHRONOUS CODE! it(@"creates the suppliers and related products and

    users", ^AsyncBlock{ [_client fetchSuppliersForUsername:@"abc" password:@"cde" withCompletion:^(NSArray *suppliers, NSError *error) { expect(suppliers).to.haveCountOf(2); expect(suppliers[0]).to.beKindOf([TXHSupplier class]); for(TXHSupplier *supplier in suppliers) { expect([supplier.products count]).to.beGreaterThan(0); expect(supplier.user.email).to.equal(@"abc"); } ! done(); }]; });
  7. EXPECTA FOR EXPECTATIONS expect(x).to.equal(y) expect(x).to.beIdenticalTo(y) expect(x).to.beNil() expect(x).to.beTruthy() expect(x).to.beFalsy() expect(x).to.contain(y) expect(x).to.beSupersetOf(y)

    expect(x).to.haveCountOf(y) expect(x).to.beEmpty() expect(x).to.beInstanceOf([Foo class]) expect(x).to.beKindOf([Foo class]) expect([Foo class]).to.beSubclassOf([Bar class]) expect(x).to.beLessThan(y) expect(x).to.beLessThanOrEqualTo (y) expect(x).to.beGreaterThan(y) expect(x).to.beGreaterThanOrEqua lTo(y) expect(x).to.beInTheRangeOf(y,z) expect(x).to.beCloseTo(y) expect(x).to.beCloseToWithin(y, z) expect(^{ /* code */ }).to.raise(@"ExceptionName") expect(^{ /* code */ }).to.raiseAny() expect(x).to.conformTo(y) expect(x).to.respondTo(y) expect(^{ /* code */ }).to.notify(@"NotificationNa me") expect(^{ /* code */ }).to.notify(notification) expect(x).to.beginWith(y) expect(x).to.endWith(y)
  8. WHEN DO I NEED TO MOCK • You want to

    specify the return from a method call. • You want to verify that a method has been called. • You want to verify that only some methods are called
  9. STUB AND RETURN // Create a stubbed class ! id

    mock = [OCMockObject mockForClass:[SomeClass class]] ! // specify a return when a method is called ! [[[mock stub] andReturn:@"Hello"] greet:[OCMArgAny]; ! // You can now test against it. expect([stub greet]).to.equal(@"Hello");
  10. VERIFY METHOD CALLS // Create a stubbed class id mock

    = [OCMockObject mockForClass:[SomeClass class]] ! // Create a expectation [[mock expect] greet:[OCMArg any]]; ! // Do something that might call the method expect([stub greet]).to.equal(@"Hello"); ! // verify that it is called expect([mock verify]).toNot.raiseAny();
  11. CONSIDERATIONS • I don’t use them much. There are other

    ways of getting results back from calls. • Sometimes, their use depends on implementation details, which can be brittle.
  12. AN ALTERNATIVE TO MOCKING • Doesn’t send out to the

    network • Store the response as a file, that is loaded in response to a network call • Easier than writing mocks and better for behavioural testing.
  13. context(@"with a valid username and password", ^{ __block __weak id<OHHTTPStubsDescriptor>

    _suppliersStub; ! before(^{ NSDictionary *httpHeaders = @{@"Content-Type" : @"application/json"}; _suppliersStub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { return [request.URL.lastPathComponent isEqualToString:@“suppliers"]; ! } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"Suppliers.json", nil) statusCode:200 headers:httpHeaders]; }]; _suppliersStub.name = @"stub with suppliers"; }); ! after(^{ [OHHTTPStubs removeStub:_suppliersStub]; });
  14. IT’S STILL A MOCK, THOUGH • It is implementation dependent,

    you need to know the endpoint, and the content of the response. • It is useful if the results change, you just need to keep it up to date. • In the context of testing with expectations, it makes sense. i.e. I do something, and I can text against the eventual expected value.
  15. “Red. Green. Refactor with confidence. If you don’t see your

    tests fail at first, you can’t be sure that they are running.”
  16. THANK YOU! NOW GO LOOK THESE UP • https://github.com/specta/specta •

    https://github.com/specta/expecta/ • http://ocmock.org • https://github.com/AliSoftware/OHHTTPStubs • https://github.com/kiwi-bdd/Kiwi • https://github.com/luisobo/Nocilla