# Testing MVC-Starter
This project now supports a dev-only Classic ASP test harness built with `aspunit`.
## Scope
- Production app: `public/`
- Dev-only test app: `tests/`
- Test framework: `tests/aspunit/`
The `tests/` IIS application assumes the repository layout keeps `tests/`, `public/`, `core/`, and `app/` as sibling directories.
## File Inventory
| Type | Path | Purpose |
| ---- | ---- | ------- |
| Create | `tests/web.config` | IIS/default-document config for the isolated test app |
| Create | `tests/unit/web.config` | Mirrored config for nested unit pages that load config-aware code |
| Create | `tests/component/web.config` | Mirrored config for nested component pages that load config-aware code |
| Create | `tests/integration/web.config` | Mirrored config for nested integration pages that load config-aware code |
| Create | `tests/sync-webconfigs.vbs` | Utility script to mirror `tests/web.config` into nested test folders |
| Create | `tests/run-tests.cmd` | Windows helper to sync configs and open the test runner URL |
| Create | `tests/bootstrap.asp` | Shared test bootstrap and runtime reset helpers |
| Create | `tests/PlainRunnerTheme.asp` | Local runner theme that removes CDN dependence from the test UI |
| Create | `tests/support/HttpCaptureHelpers.asp` | Shared HTTP capture helpers for rendered-page assertions |
| Create | `tests/test-manifest.asp` | Single source of truth for registered test pages |
| Create | `tests/run-all.asp` | Browser runner that aggregates all test pages |
| Vendor | `tests/aspunit/Lib/*` | Upstream aspunit framework files |
| Vendor | `tests/aspunit/LICENSE-MIT` | Upstream aspunit license for vendored third-party code |
| Create | `tests/unit/TestHelpers.asp` | Deterministic helper-function unit tests |
| Create | `tests/unit/TestControllerRegistry.asp` | Controller whitelist/format unit tests |
| Create | `tests/component/TestHomeController.asp` | Controlled component-level controller test |
| Create | `tests/integration/TestMvcDispatch.asp` | Narrow router/dispatch smoke test |
| Create | `tests/integration/TestRoutes.asp` | Route-helper/config integration coverage |
| Create | `tests/integration/TestConfigSettings.asp` | Nested config and fallback behavior coverage |
| Create | `tests/integration/TestRenderedOutput.asp` | Production-page output assertions through safe HTTP capture |
| Create | `tests/integration/TestSharedLayout.asp` | Shared header/footer/layout assertions against rendered production pages |
| Reference | `public/web.config` | Source of mirrored config keys for the test app |
| Reference | `core/helpers.asp` | Helper functions and config-loading behavior under test |
| Reference | `core/mvc.asp` | Dispatcher behavior used by the smoke test |
| Reference | `core/lib.ControllerRegistry.asp` | Whitelist behavior under test |
| Verify | `public/` site | Confirm production app exposes no test routes/pages |
## IIS Setup
### Option A: IIS Express via the checked-in `applicationhost.config` (quickest)
This repo ships an `applicationhost.config` (used by `run_site.cmd`) that defines two IIS Express sites:
- `Development Web Site` - `public/` on `http://localhost:8080/`
- `Tests Web Site` - `tests/` on `http://localhost:8081/`
`` is set server-wide in that same file - this is what lets `tests/bootstrap.asp` and the integration pages `#include` sibling files from `../core/` and `../app/` (physically outside the `tests/` site's own root). No per-app IIS configuration is required.
A single `iisexpress.exe` process only ever runs **one** site from a config file - per `iisexpress /?`: "`/config:config-file` ... runs the first site in the specified configuration file." `serverAutoStart="true"` is an IIS/W3SVC (full IIS) concept and is not honored by a standalone `iisexpress.exe` invocation, so listing a second `` is not enough on its own.
1. Run `run_site.cmd` - it launches **two** `iisexpress.exe` processes, each pinned to one site with `/site:"..."` (each opens its own console window so you can see that site's request log).
2. Browse to `http://localhost:8081/` (or run `tests\run-tests.cmd`, which syncs configs first, then opens the runner URL).
3. `tests/web.config`'s `ProductionAppBaseUrl` is set to `http://localhost:8080/` to match the production site's actual binding above - `TestRenderedOutput.asp`/`TestSharedLayout.asp` use this to fetch real rendered pages over HTTP. If you rebind either site to a different port, update this value too, update the matching `/site:`/binding in `applicationhost.config` and `run_site.cmd`, and re-run the sync script (step 5 below).
If `applicationhost.config` only has the `Development Web Site` entry (e.g. in a checkout from before the `Tests Web Site` entry was added), that's why the tests app has nothing to browse to - add a second `` block for `tests/` following the same shape as the existing one, on its own port.
### Option B: A real IIS server
1. Keep the existing production IIS app rooted at `public/`.
2. Create a separate development-only IIS application rooted at `tests/`.
3. Enable Classic ASP for that IIS app.
4. Ensure parent paths are allowed for the `tests/` app - in IIS Manager this is the "Enable Parent Paths" checkbox under ASP settings for that application (or `` in that app's own web.config). `tests/web.config` does not set this itself; on IIS Express it comes from the server-wide `` element in `applicationhost.config` instead (see Option A).
5. Browse to the `tests/` app root or directly to `run-all.asp`.
6. If you change `tests/web.config`, run `cscript //nologo tests\sync-webconfigs.vbs` to refresh the nested copies used by the unit, component, and integration pages.
7. Set `ProductionAppBaseUrl` in `tests/web.config` to wherever the production app actually resolves (e.g. `http://localhost/` for a root site, or `http://localhost/MyClassicApp/` for a virtual-directory app) and re-run the sync script so rendered-output tests know where to send HTTP requests.
Example layout:
- Production: `http://localhost/`
- Tests: `http://localhost/tests-dev/`
## How It Works
- `tests/run-all.asp` includes the aspunit library and the manifest.
- `tests/run-all.asp` also applies `PlainRunnerTheme.asp`, a local runner theme that avoids the upstream CDN dependency in aspunit’s default UI.
- `tests/test-manifest.asp` explicitly registers each test page.
- `tests/bootstrap.asp` provides the shared runtime setup:
- helper/config access
- controller registry access
- router reset
- `tests/support/HttpCaptureHelpers.asp` provides safe HTTP-based page capture for rendered output assertions against the production app.
The integration test page includes `core/mvc.asp` directly because that is the only first-wave test that needs dispatcher behavior.
Because `GetAppSetting()` uses `Server.MapPath("web.config")`, nested test folders also need a mirrored `web.config` alongside the executing test pages that rely on config-aware runtime files. Use `tests/sync-webconfigs.vbs` after changing `tests/web.config`.
The manifest is manual by design. There is no filesystem auto-discovery.
## Running Tests
Open the browser runner:
Browse to `run-all.asp` within the `tests/` IIS application, for example:
```text
http://localhost/tests-dev/run-all.asp
```
aspunit renders a UI in runner mode and loads each registered page with `?task=test` behind the scenes.
On Windows you can also use:
```bat
tests\run-tests.cmd
```
Or with an explicit runner URL:
```bat
tests\run-tests.cmd http://localhost:8085/run-all.asp
```
## Adding a New Test Page
1. Choose the right folder:
- `tests/unit/` for deterministic helper or registry tests
- `tests/component/` for direct controller/object tests with controlled setup
- `tests/integration/` for narrow runtime smoke coverage, config behavior, or rendered-page capture
- shared layout assertions belong here too, because they verify rendered production responses rather than isolated helper behavior
2. Create a new `.asp` file that:
- includes `../aspunit/Lib/ASPUnit.asp`
- includes `../bootstrap.asp`
- registers one or more modules with `ASPUnit.AddModule(...)`
- calls `ASPUnit.Run()`
3. Add the page path to `tests/test-manifest.asp`.
4. Reload `run-all.asp`.
## First-Pass Isolation Policy
- Unit tests should avoid mutating globals.
- Registry tests should inspect current behavior, not expand the production whitelist.
- Component tests should reset touched singleton/controller state in setup/teardown.
- Dispatch smoke tests should initialize fresh route state before each run.
## What Not To Unit Test
- Full rendered page markup for shared-layout pages unless you add reliable output-capture support.
- Production IIS rewrite behavior from `public/`.
- Broad end-to-end request flows through the production site root.
- Config-dependent behavior that requires machine-specific production values unless you first mirror safe test values into `tests/web.config`.
- Internet access as a requirement for the runner UI. The local theme removes the upstream CDN dependency for first-wave execution.
## Validation Checklist
- `run-all.asp` loads in the separate `tests/` IIS app.
- All manifest pages appear in the runner.
- Helper, registry, component, and integration suites all execute.
- Route-helper/config integration assertions execute from the same isolated IIS app.
- Rendered-page capture assertions can verify production HTML and status codes without polluting aspunit JSON responses.
- Shared layout assertions can verify navbar, asset links, titles, and footer script presence across production-rendered pages.
- Re-running the suite produces stable results.
- The production site under `public/` still exposes no test runner pages or test routes.
## Limitations
- This harness runs only inside IIS/Classic ASP; it is not intended for Linux execution.
- The repo’s production runtime is still configured via `public/web.config`; the test app uses a minimal mirrored config in `tests/web.config`.
- If the narrow bootstrap is not sufficient for future smoke tests, broaden only the test bootstrap first before considering production runtime changes.