Visitar URL original
'afterEach' cannot be used inside 'it', 'afterEach' may only be used inside 'context' or 'describe' · Issue #1253 · Quick/Quick · GitHub
Skip to content

'afterEach' cannot be used inside 'it', 'afterEach' may only be used inside 'context' or 'describe' #1253

Description

@alexdmotoc
  • I have read CONTRIBUTING and have done my best to follow them.

What did you do?

I am writing a test helper to help me find memory leaks.

extension QuickSpec {
  func trackForMemoryLeaks(
    sut: AnyObject,
    file: String = #filePath,
    line: UInt = #line
  ) {
    afterEach { [weak sut] in
      expect(file: file, line: line, sut).to(beNil())
    }
  }
}

When I initialise my SUT I have a method like so:

extension MyTestSuite {
  func makeSUT(
    file: String = #filePath,
    line: UInt = #line
  ) -> MySUT {
    let sut = MySUT()
    trackForMemoryLeaks(sut, file: file, line: line)
    return sut
  }
}

The problem is that in our company we have the convention of creating the SUT inside the it block. So when doing so I get the error 'afterEach' cannot be used inside 'it', 'afterEach' may only be used inside 'context' or 'describe'.

I tried to replace the afterEach with addTeardownBlock but in that case the teardown block is never called. Is there any way to add support for afterEach to be run inside the it block, or add support for addTeardownBlock to be called after each it statement finishes?

Further, if I move my makeSUT call inside a describe or context scope, the SUT is never deallocated because it's held in memory by that describe or context scope, so I can't test properly that it's being deallocated.

So I assume the solution to this is to add support for addTeardownBlock inside an it statement.

What did you expect to happen?

addTeardownBlock should be called after the it statement.

What actually happened instead?

addTeardownBlock was never called, regardless where of the scope it was declared in (it, describe or context)

Environment

List the software versions you're using:

  • Quick: 5.0.1
  • Nimble: 10.0.0
  • Xcode Version: 14.3.1
  • Swift Version: Xcode Default

Please also mention which package manager you used and its version. Delete the
other package managers in this list:

  • CocoaPods: 1.12.1

Activity

  1. alexdmotoc commented on Nov 24, 2023

    @alexdmotoc
    Author

    Also, it seems there is no way to test for memory leaks. The following example shows a memory leak and a test against it that is passing (it should fail). What is the correct approach for testing against memory leaks?

    class MyService {
        var completion: (() -> Void)?
    
        func perform(completion: @escaping () -> Void) {
            self.completion = completion
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: completion)
        }
    }
    
    class MyObject {
        var didPerform = false
        let service = MyService()
    
        func perform(completion: @escaping () -> Void) {
            service.perform {
                self.didPerform = true
                completion()
            }
        }
    }
    
    final class MockTest: QuickSpec {
        override func spec() {
            describe("mock test") {
                var sut: MyObject!
    
                beforeEach {
                    sut = MyObject()
                }
    
                afterEach { [weak sut] in
                    expect(sut).to(beNil())
                }
    
                it("performs") {
                    sut.perform {}
                    expect(sut.didPerform).toEventually(beTrue())
                }
            }
        }
    }
  2. brandtdaniels commented on Feb 13, 2024

    @brandtdaniels

    context and describe are the same code implementation, just different meanings in the bdd world. Typically describe is used to describe your subject under test, while the context is used to describe your scenario for either interaction or state testing. The it block is only meant to contain a single test.

    For example

    describe("Some widget factory") {
    
      var subject: WidgetFactoryProtocol!
      var mockNetwork: NetworkProtocol!
    
      beforeEach {
         mockNetwork = MockNetwork()
          subject = WidgetFactoryImpl(network: mockNetwork)
       }
    
        context("when the network request fails") {
            beforeEach {
                // Setup your scenario described in the context
               mockNetwork.shouldSucceed = false
            }
            it("should fail to produce a widget")  {
                subject.produceWidget()
                expect(subject.didProduceWidget).to(beFalse())
           }
        }
    
      context("when the network request succeeds") {
          beforeEach {
             // Setup your scenario described in the context
             mockNetwork.shouldSucceed = true
          }
          ........
      }
    }
    
    

    same with afterEach as it's meant to reset things after your specific scenario if you're not reinitializing your subject within each context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions