heroes
, by writing automated tests for it. We will also set up configurable environments for our application.heroes
application needs to be able to configure the username, password, host port and name of the database it uses. Open the file config.yaml
, which is empty, and enter the following key-value pairs:lib/channel.dart
, declare a new class at the bottom of the file:Configuration
subclass declares the expected properties of a configuration file. HeroConfig
has one property named database
- this matches the name of our top-level key in config.yaml
. A DatabaseConfiguration
is a built-in configuration type that has properties for host
, port
, username
, password
and databaseName
. We can load config.yaml
into a HeroConfig
because they have the same structure and all of the key names match the property names in our configuration types.config.yaml
and use its values to set up our database connection by replacing the prepare
method in lib/channel.dart
:options
property that has the command-line arguments that started the application. By default, the value of configurationFilePath
is config.yaml
(it corresponds to --config-path
in conduit serve
). When config.yaml
is read, its values are read into a HeroConfig
and are used to configure our database connection.config.yaml
into version control because it contains sensitive information. However, it is important to check in a configuration source file. A configuration source file has the same structure as HeroConfig
, but it has values for your test environment - both locally and with continuous integration tools. It is also used as a template for your deployed configuration files.$
prefix as a value, e.g. password: $DATABASE_PASSWORD
.config.src.yaml
, and one currently exists as an empty file in your project. Enter the following configuration into this file:conduit_test
and test
was already added to your pubspec.yaml
file as a test dependency by the template generator.main
function. In this function, the test
function is called multiple times to register expectations. A test passes if all of your expectations are met. An example Dart test looks like this:config.src.yaml
, we target the database dart:[email protected]:5432/dart_test
. This is a 'special' database that is used by all Conduit applications for automated testing (by default). When your application is tested, its tables are temporarily added to this database and then discarded after tests complete. This means that no data is stored in between test runs.test/hero_controller_test.dart
._test.dart
and must be in the test/
directory of your project, or it won't be run.main
function:install
method. This harness can then send requests to your application, and you can expect that the response is correct. Add a test to the main function that makes sure we get back a 200 OK when we call GET /heroes
:Agent
that can send requests to the application it started. Methods like get
and post
take a path (and optionally headers and a body) and return a response object. This object is used in expectResponse
to validate the status code and other values. Tests in Conduit are written in this way: make a request, expect that the response is intended.test/harness/app.dart
, mixin TestHarnessORMMixin
and override two methods:resetData
. This method deletes everything from the test database and uploads the schema in a pristine state. By calling this method in onSetUp
, our test harness will reset data before each test.-t
command-line argument with conduit create
allows you to select a template. Templates like db
and db_and_auth
have a test harness that already mixes in TestHarnessORMMixin
.main
function in hero_controller_test.dart
and selecting Run tests in 'hero_controller_test.dart'
. A panel will appear that shows the results of your tests. You'll see a green checkmark next to the test in this panel to show that your test succeeded. If your test did not succeed, the reason will be printed to the console. If your test failed because of an error in your code, you will also be able to see the stack trace of the error.pub run test
from your project's directory. You can re-run a test with the green play button at the top right corner of the screen, or the keyboard shortcut associated with it (this shortcut varies depending on your installation).id
greater than 0, and a name
that is a string. When expecting a body value, the body is first decoded from its content-type before the expectation. In practice, this means that your JSON response body is deserialized into an object or list. Your expectations of the body are built from Dart objects like List
and Object
that deserialized from JSON.everyElement
is a Matcher
from package:matcher
. There are many types of matchers for all kinds of scenarios, and package:conduit_test
includes Conduit-specific matchers. See the conduit_test API Reference for all Conduit matchers.hero.dart
at the top of the file!POST /heroes
. In the first test, we'll make a mistake on purpose to see how tests fail. Add the following test:name
is a unique property of a hero. Notice that the first request didn't fail, even though we had created a 'Fred' hero in the previous test - that's because we reset the database for each test in our harness.