Testing

General Test Helpers

The TailorTesting framework provides special test case classes to make it easier to write tests for Tailor apps. The core class is TailorTestCase. This provides implementations of setUp and tearDown that prepare your application for testing. setUp will call a configure method on the test case that allows you to set special configuration on your app when it's in a testing mode. It will also call a resetDatabase method that will reset your test database when the first test case starts, and will clear out all the tables between test cases. It will also call Timestamp.freeze to freeze the current time, so that all code within a test case will see the same value for the current time. This means that you can test code that produces results based on the current time without worrying that the time will change between when your app's code runs and when the test case verifies the result. You should have all of the test cases in your app subclass TailorTestCase so that you can get this behavior automatically. The configure method is defined dynamically, so you can create an extension on TailorTestCase to provide a different implementation, and that implementation will be used for all classes in your test target that subclass TailorTestCase.

The TailorTestCase class also provides some test matchers on the test case instance, which can provide a more readable syntax than the global XCTAssert functions.

Controller Tests

The ControllerTestCase type provides some special helpers for test cases that test the behavior of a controller. It has a few fields that you should set in the setUp method that you should set to prepare your test cases. The controllerType field specifies what kind of controller you are testing, which allows us to generate requests and paths in a concise way. The params field provides default parameters for requests; it maps action names to a hash of request parameters for that action. The user field allows you to specify a user for test requests, so that you can simulate the behavior when a user is signed in. Finally, the files field provides uploaded file data for test requests.

Once you provide that information, you can use the callAction method on the test case to trigger an action. It takes a callback that receives the response from the controller and can run additional checks. If the controller never responds, the callAction method will record a failure.

There are also a few methods for testing response. If you want to test whether a response rendered a given template, you can call assert(response, renderedTemplate: templateType). If you want to test whether it redirected to a given path, you can call assertResponse(response, redirectsTo: path). You can also build paths by calling pathFor on the test case. Finally, you can check for text in the response body by calling assertResponse(response, contains: text).

Controller Test Example

class HatsControllerTests: ControllerTestCase {
  override func setUp() {
    super.setUp()
    controllerType = HatsController.self
    params = [
      "create": [
        "hat[color]": "Black",
        "hat[brimSize]": "15"
      ] 
    ]
  }

  func testCreateActionWithValidParamsCreatesHat() {
    callAction("create") {
      _ in
      let hat = Hat.query.last()
      self.assert(hat.color, equals: "Black")
      self.assert(hat.brimSize, equals: 15)
    }
  }

  func testCreateActionWithValidParamsRedirectsToIndexPage() {
    callAction("create") {
      response in
      self.assert(response, redirectsTo: pathFor(actionName: "index"))
    }
  }

  func testCreateActionWithNoColorRendersForm() {
    params["create"]?["color"] = ""
    callAction("create") {
      response in
      self.assert(response, renderedTemplate: HatsController.FormTemplate.self)
    }
  }
}

Template Tests

The TemplateTestCase type provides some special helpers for test cases that test the behavior of a template. Like ControllerTestCase, it has some fields for providing some context about what you are testing. The template field provides the field contains the template that we are testing. The controller field provides the controller that is rendering the template. You can populate this by calling setUpController and then use it when building your template. Once you call generate on the template, you can get its rendered body in the contents field.