Skip to content

Commit

Permalink
Add IPassGenerator interface for DI and testing
Browse files Browse the repository at this point in the history
Conform to the `IPassGenerator` interface in `PassGenerator` to allow for dependency injection and testing.

Write a simple section in README to explain the purpose of the interface and how to use it.
  • Loading branch information
eimantas committed Oct 27, 2024
1 parent 370ebaf commit 7c4cc05
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 18 deletions.
23 changes: 23 additions & 0 deletions Passbook.Generator/IPassGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
public interface IPassGenerator
{
/// <summary>
/// Creates a byte array which contains one pkpass file
/// </summary>
/// <param name="generatorRequest">
/// An instance of a PassGeneratorRequest</param>
/// <returns>
/// A byte array which contains a zipped pkpass file.
/// </returns>
public byte[] Generate(PassGeneratorRequest generatorRequest);

/// <summary>
/// Creates a byte array that can contains a .pkpasses file for bundling multiple passes together
/// </summary>
/// <param name="generatorRequests">
/// A list of PassGeneratorRequest objects
/// </param>
/// <returns>
/// A byte array which contains a zipped pkpasses file.
/// </returns>
public byte[] Generate(IReadOnlyList<PassGeneratorRequest> generatorRequests);
}
19 changes: 1 addition & 18 deletions Passbook.Generator/PassGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Passbook.Generator;

public class PassGenerator
public class PassGenerator: IPassGenerator
{
private byte[] passFile = null;
private byte[] signatureFile = null;
Expand All @@ -24,14 +24,6 @@ public class PassGenerator
private byte[] pkPassBundle = null;
private const string passTypePrefix = "Pass Type ID: ";

/// <summary>
/// Creates a byte array which contains one pkpass file
/// </summary>
/// <param name="generatorRequest">
/// An instance of a PassGeneratorRequest</param>
/// <returns>
/// A byte array which contains a zipped pkpass file.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public byte[] Generate(PassGeneratorRequest generatorRequest)
{
Expand All @@ -46,15 +38,6 @@ public byte[] Generate(PassGeneratorRequest generatorRequest)
return pkPassFile;
}

/// <summary>
/// Creates a byte array that can contains a .pkpasses file for bundling multiple passes together
/// </summary>
/// <param name="generatorRequests">
/// A list of PassGeneratorRequest objects
/// </param>
/// <returns>
/// A byte array which contains a zipped pkpasses file.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentException">
public byte[] Generate(IReadOnlyList<PassGeneratorRequest> generatorRequests)
Expand Down
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,84 @@ return new FileContentResult(generatedBundle, "application/vnd.apple.pkpasses")
};
```

### Testing pass generator

#### Strategy 1: Mocking and DI

`PassGenerator` class conforms to `IPassGenerator` interface that exposes methods used to generate the passes. If you have some custom logic that builds the pass generation request, you can use easily mock the `IPassGenerator` interface using any mocking library and test whether your logic calls the `Generate` method with correct generator request.

Say you have a service that receives a generator request through DI and generates a pass based on the request.

```cs
class PassGeneratorService(IPassGenerator passGenerator)
{
public byte[] GeneratePassWithLogoTextAndBackgroundColor(String logoText, String backgroundColor)
{
// make a request based on parameters
....
passGenerator.Generate(request);
}
}
```

You can easily test this service by mocking the `IPassGenerator` interface and verifying that the `Generate` method is called with the correct request. Here's a sample using NSubstitute and xUnit.

```cs
[Fact]
void ServiceUsesPassedParamsForRequest()
{
// Arrange
var passGeneratorMock = Substitute.For<IPassGenerator>();
var sut = new PassGeneratorService(passGeneratorMock);

// Act
sut.GeneratePassWithLogoTextAndBackgroundColor("Cup'o'Joe", "#0CAFE0");

// Assert/Verify
passGeneratorMock.Received().Generate(Arg.Is<PassGeneratorRequest>(r =>
{
r.LogoText == "Cup'o'Joe" && r.BackgroundColor == "#0CAFE0"
}));
}
```

#### Strategy 2: Testing generator request instead

Another way to test if your logic works well is to test the created `PassGeneratorRequest` object itself. You can create a request object and set the properties based on your logic and then test if the request object is created correctly. Consider this basic request builder:

```cs
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor(String logoText, String backgroundColor)
{
var request = new PassGeneratorRequest();
request.LogoText = logoText;
request.BackgroundColor = backgroundColor;
return request;
}
}
```

You can test this builder by creating a request object and verifying if the properties are set correctly.

```cs
[Fact]
void RequestBuilderSetsPropertiesCorrectly()
{
// Arrange
var sut = new PassGeneratorRequestBuilder();

// Act
var request = sut.BuildRequestWithLogoTextAndBackgroundColor("Cup'o'Joe", "#0CAFE0");

// Assert
Assert.Equal("Cup'o'Joe", request.LogoText);
Assert.Equal("#0CAFE0", request.BackgroundColor);
}
```

Now you only have to make sure that the request is not changed/mutated on its way to the `PassGenerator` class.

### Troubleshooting Passes

If the passes you create don't seem to open on iOS or in the simulator, the payload is probably invalid. To aid troubleshooting, I've created this simple tool - https://pkpassvalidator.azurewebsites.net - just run your `pkpass` file through this and it might give some idea what's wrong. The tool is new (Jul'18) and doesn't check absolutely everything. I'll try and add more validation to the generator itself.
Expand Down

0 comments on commit 7c4cc05

Please sign in to comment.