Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
alexdmotoc opened this issue Nov 22, 2023 · 2 comments

Comments

@alexdmotoc
Copy link

alexdmotoc commented Nov 22, 2023

  • 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
@alexdmotoc
Copy link
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())
            }
        }
    }
}

@brandtdaniels
Copy link

brandtdaniels commented Feb 13, 2024

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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants