Migrate docs to mkdocs

feature/mock
Richard Körber 2019-12-07 14:26:05 +01:00
parent f49be83c47
commit 9e4fcf1429
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
5 changed files with 187 additions and 0 deletions

View File

@ -50,6 +50,11 @@ acme4j-utils
The Java module name is `org.shredzone.acme4j.utils`. The Java module name is `org.shredzone.acme4j.utils`.
acme4j-mock
: [`acme4j-mock`](https://mvnrepository.com/artifact/org.shredzone.acme4j/acme4j-mock/latest) contains a framework for unit testing your _acme4j_ client.
The Java module name is `org.shredzone.acme4j.mock`.
acme4j-example acme4j-example
: This module only contains [an example code](https://shredzone.org/maven/acme4j/acme4j-example/apidocs/src-html/org/shredzone/acme4j/ClientTest.html) that demonstrates how to get a certificate with _acme4j_. It depends on `acme4j-client` and `acme4j-utils`. It is not useful as a dependency in other projects. : This module only contains [an example code](https://shredzone.org/maven/acme4j/acme4j-example/apidocs/src-html/org/shredzone/acme4j/ClientTest.html) that demonstrates how to get a certificate with _acme4j_. It depends on `acme4j-client` and `acme4j-utils`. It is not useful as a dependency in other projects.

View File

@ -0,0 +1,8 @@
# How to Test acme4j Clients
_acme4j_ comes with a mock framework. It will help you writing unit tests for your client.
This chapter describes how the mock framework is used.
* [About the Mock Framework](mock.md)
* [Simple Unit Tests](simple.md)

View File

@ -0,0 +1,31 @@
# About the Mock Framework
_acme4j_ is designed to make access to ACME servers as easy and transparent as possible. The API is designed to feel straightforward. You won't even know when the ACME server is actually accessed. This makes writing an ACME client rather easy, but writing unit tests for it is hell.
The `acme4j-mock` module offers a unit testing framework that is supposed to make unit testing a lot easier. All you need to do is add it as `test` scoped dependency to your application.
## How it works
Essentially, the ACME protocol exchanges signed JSON data via HTTP. On the _acme4j_ side, each ACME request is passed to the `org.shredzone.acme4j.connector.Connection` interface. The implementation takes care for signing and sending the request, parsing the response, and do some other necessary things.
This is where the mock framework comes in. It provides an own `Connection` implementation, which "connects" to a mock ACME server instead of a real server. All the communication is done internally, without actual HTTP requests being sent to a real server. This is exactly what we would want for unit tests. They run in an environment that is closed in itself, and do not depend on the presence of external resources (like a network connection).
Also, the unit test has full control over the mock ACME server. The test can prepare the server as necessary, and it can also simulate errors and rare corner cases.
## Caveats
The mock server is just this: a mockup. It's not a fully-blown ACME server, far from it. This is not the purpose of this framework.
For this reason, there are a few caveats to remember when writing unit tests using the `acme4j-mock` framework.
* The mock server is a very simple implementation. It does not perform all the elaborate validation steps of a real ACME server. It behaves differently than a real ACME server in many situations. It is not meant for integration tests, but just for simple unit tests.
* Since no real HTTP requests are taking place, all the URLs involved are plain virtual, and cannot be accessed (e.g. by a HTTP client).
* It also means that there is no actual domain validation taking place. You won't need to run a HTTP or DNS server. If a challenge shall be deemed as failed, just change its status at the ACME server accordingly.
* The URLs are purely random. Do not assume a fixed structure of the URLs. Every URL may look completely different on the next run of the unit test, or the next _acme4j_ version.
* You cannot access the mock server by an `acme` URI. To access it, you must always use the `Session` that is generated by the mock server (see next chapter).
* The mock framework is not threadsafe.
* It is not meant to be a replacement for integration tests. It is still recommended to run full integration tests with your code (e.g. against a [Pebble](https://github.com/letsencrypt/pebble) server).
## Experimental!
The mock framework is still experimental. As feedback comes in, there will surely be some parts that need improvement. Expect breaking API changes in the next couple of _acme4j_ versions.

View File

@ -0,0 +1,138 @@
# Simple Unit Tests
`org.shredzone.acme4j.mock.MockAcmeServer` is the central class of the mock framework. It mocks an ACME server instance.
So, the first thing to do in every unit test is to create an instance of this server:
```java
MockAcmeServer server = new MockAcmeServer();
```
The server provides a `Session` instance that can be used to access the server.
```java
Session session = server.createSession();
```
!!! note
This is the only way to create a `Session` that is connected to the mock server! Even though the mock server seems to have an ACME URI (`"acme://mock/"`), you cannot create a new `Session` by providing this URI.
`MockAcmeServer` are lightweight. You can just create a new instance for each unit test, and throw it away at the end of the test.
## Basic Usage
You can use the Session as if you were connected to a real ACME server. For example, this is how to create a new account:
```java
KeyPair keyPair = KeyPairUtils.createKeyPair(2048);
URI email = URI.create("mailto:foo@example.com");
Account account = new AccountBuilder()
.addContact(email)
.agreeToTermsOfService()
.useKeyPair(keyPair)
.create(session);
assertThat(account.getLocation(), is(notNullValue()));
assertThat(account.getStatus(), is(Status.VALID));
assertThat(account.getContacts().size(), is(1));
assertThat(account.getContacts().get(0), is(email));
```
## Mock Resources
The mock server manages a mock resource for each resource that is created in the test. For example, an `Account` on your client side is matched by a `MockAccount` instance on server side. It reflects the server's status of that account.
You can get the `MockAccount` from the `MockAcmeServer` in different ways:
```java
// Get the MockAccount that is matching your Account instance
MockAccount mockAccount = server.getMockOf(account);
// Get the MockAccount by the public key
MockAccount mockAccountByKey = server.findAccount(keyPair.getPublic()).get();
// Just get all the MockAccounts that are currently existing
List<MockAccount> allMockAccounts = server.getAccounts();
```
The `MockAccount` reflects the state of your account on the server side:
```java
assertThat(mockAccount.getContacts().size(), is(1));
assertThat(mockAccount.getContacts().get(0), is(email));
```
You can manipulate the `MockAccount` at will. For example, you can change its status:
```java
// By default, your account is VALID
assertThat(account.getStatus(), is(Status.VALID));
// We are going to change that now...
mockAccount.setStatus(Status.REVOKED);
// Your local Account object still holds the cached status!
assertThat(account.getStatus(), is(Status.VALID));
// Update it, so the new status is fetched from the server
account.update();
assertThat(account.getStatus(), is(Status.REVOKED));
```
!!! note
You can always enforce a resource status by setting the desired status value via `setStatus()`. If you set no status (or set the status to `null`), the mock server deduces the status from the current state of the resource. For most test cases, you can just leave the status untouched and let the mock server take care for it.
## Setting Up Mock Resources
Mock resources also work the other way around. You can create them on the server side, and populate the mock server with resources before running a test against it.
```java
MockAcmeServer server = new MockAcmeServer();
// Create a key pair and contact address
KeyPair keyPair = KeyPairUtils.createKeyPair(2048);
URI email = URI.create("mailto:foo@example.com");
// Create a new MockAccount
MockAccount mockAccount = server.createAccount(keyPair.getPublic());
mockAccount.setTermsOfServiceAgreed(true);
mockAccount.getContact().add(email);
URL accountLocation = mockAccount.getLocation();
// Your account is ready, just use it in your test...
Session session = server.createSession();
Login login = session.login(accountLocation, keyPair);
Account account = login.getAccount();
assertThat(account.getContacts().get(0), is(email));
```
The entire mock server can be prepared for a test that way. For example, if you want to write a unit test for a method that only downloads an existing ceritificate, you can construct a server with all the necessary resources being already present. When your unit test is executed, it finds a mock server that is ready for certificate downloading.
You will find more examples in the [example package](https://github.com/shred/acme4j/tree/master/acme4j-mock/src/test/java/org/shredzone/acme4j/mock/example) at the acme4j-mock unit tests.
## Shortcuts
The mock server is not a true ACME server. The advantage is that you can prepare it with a minimal effort, even if the result would not be possible on a real server. For example, if you want to test a method that only needs an `Order` resource, you can skip setting up an account, authorizations and challenges.
This is all you need to create a mock server with an order:
```java
MockAcmeServer server = new MockAcmeServer();
MockOrder mockOrder = server.createOrder(Identifier.dns("example.com"));
```
You can also generate a `Login` without having to set up an account. The mock server will create an empty account with a random key pair for you.
```java
Login login = server.createLogin();
Session session = login.getSession();
```
Now bind the location of the mock order to the `Login`, and get an `Order` resource that is ready for testing:
```java
Order order = login.bindOrder(mockOrder.getLocation());
```
All of this took just five lines of code.

View File

@ -20,6 +20,7 @@ nav:
- JavaDocs: - JavaDocs:
- 'acme4j-client': acme4j-client/apidocs/index.html - 'acme4j-client': acme4j-client/apidocs/index.html
- 'acme4j-utils': acme4j-utils/apidocs/index.html - 'acme4j-utils': acme4j-utils/apidocs/index.html
- 'acme4j-mock': acme4j-mock/apidocs/index.html
- 'acme4j-it': acme4j-it/apidocs/index.html - 'acme4j-it': acme4j-it/apidocs/index.html
- Usage: - Usage:
- 'usage/index.md' - 'usage/index.md'
@ -37,6 +38,10 @@ nav:
- 'ca/index.md' - 'ca/index.md'
- 'ca/letsencrypt.md' - 'ca/letsencrypt.md'
- 'ca/pebble.md' - 'ca/pebble.md'
- Testing:
- 'testing/index.md'
- 'testing/mock.md'
- 'testing/simple.md'
- Development: - Development:
- 'development/index.md' - 'development/index.md'
- 'development/provider.md' - 'development/provider.md'